心得
两个数论不会,惨遭爆零,我太菜了……
网络赛好难啊,我太难了……
赛中通过
H.Holy Grail(签到/6次SPFA)
题意
n(n<=300)个点,m(m<=500)条边的有向图,无重边自环负环,但有负权边
要求你加六条边,第i次的图是建立在第i-1次加边加好的前提下的,
第i次给定si ti,要求添加最小的权值的边si->ti,
可以加负边,但要求使得加边之后的图无负环
题目保证,初始情况下无si->ti的边
题解
每次以t为源点跑最短路,找到t->s的最短路p,加权值为-p的边构成零环即可,
加完边之后,重新跑最短路
号被销了 代码找不到了
F. Greedy Sequence(set维护区间最值)
题意
给定n(n<=1e5)的一个排列a[],和一个k
对于每个长度i(1<=i<=n),要求构造一个长度为n的s[]序列,满足
①第一个数是i ②s[]中相邻的两个数,满足后数比前数小
③s[]中相邻的两个数,在a[]中对应的位置,不能超过给定的k
④对于某一位置,若找不到这样的数,该位填0
⑤应使构造的序列,字典序尽可能大
要求输出对于每一个i,其构造的序列中,非0的元素个数
题解
显然贪心,对于s应找满足和s在原序列里距离不超过k的最大的数t即可,
因为t的序列已经构造好了,所以沿用即可,
用set维护一个[i-k,i+k]的窗口,找到每个i的后继j,统计即可
其实set还是很不熟练,要多敲
注意q.upper_bound(v)比upper_bound(q.begin(),q.end(),v)快很多
用set维护当前[i-k,i+k]的窗口,每次在set中寻找小于a[l]的最大的值,
即去寻找大于-a[l]的最小的值,取反即可
代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int T,a[N],n,k,l,r;
int to[N],ans[N];
set<int>q;
int main()
{
scanf("%d",&T);
while(T--)
{
scanf("%d%d",&n,&k);
for(int i=1;i<=n;++i)
scanf("%d",&a[i]);
q.clear();
q.insert(0);
l=r=1;
for(int i=1;i<=n;++i)
{
while(r<=n&&r<=i+k)q.insert(-a[r++]);
while(l<=r&&l<i-k)q.erase(-a[l++]);
to[a[i]]=-(*q.upper_bound(-a[i]));
}
for(int i=1;i<=n;++i)
{
ans[i]=ans[to[i]]+1;
printf("%d%c",ans[i],i==n?'\n':' ');
}
}
return 0;
}
赛后补题
A.The beautiful values of the palace(找规律+BIT/cdq)
题意

如上,回旋矩阵,左下角(1,1),右上角(n,n),(i,j)位置的价值定义为(i,j)处的数的数位和
T(T<=5)组样例,每次给定矩阵内n(n<=1e6)个点,只有这些点有价值,其余处为0,
m(m<=1e5)次询问,每次给出坐标(x1,y1,x2,y2),询问左下角(x1,y1)右上角(x2,y2)的矩阵中的价值和
题解
先搞出矩阵的规律,然后考虑维护二维前缀和,
树状数组可以直接做,而且更简洁,
把询问处理成四个矩阵,搞二维偏序下的二维前缀和,
这里加了时间一维,也没排序自然就离线了,
刚学的cdq,纯属故意用cdq凑三维偏序(时间,y,x)裸题
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=4e6+1e5+10;
const int M=1e5+10;
int t,n,m,p,x,y,cnt;
int f,g,c,d;
int ans[M];
int to(int x,int y)
{
x=x-n/2-1;
y=y-n/2-1;
int t=max(abs(x),abs(y));
ll ans;
if(x>=y)ans=1ll*n*n-4ll*t*t-2*t-x-y;
else ans=1ll*n*n-4ll*t*t+2*t+x+y;
int res=0;
for(;ans;ans/=10)
res+=ans%10;
return res;
}
struct node
{
int op,x,y,w,id;
bool operator<(const node &a)
{
return y<a.y||(y==a.y&&op<a.op);
}
}e[N],b[N];
struct BIT
{
const static int N=1e6+10;
int n,tr[N];
void init(int _n)
{
n=_n;
memset(tr,0,sizeof tr);
}
void add(int x,int v)
{
for(int i=x;i<=n;i+=i&-i)
tr[i]+=v;
}
int sum(int x)
{
int sum=0;
for(int i=x;i>0;i-=i&-i)
sum+=tr[i];
return sum;
}
}tr;
void cdq(node *a,int l,int r)
{
if(l==r)return;
int m=(l+r)/2;
//分治处理子区间
cdq(a,l,m);
cdq(a,m+1,r);
//处理左区间对右区间的影响
int s=l,t=m+1;
for(int i=l;i<=r;++i)
{
if(t>r||s<=m&&a[s].y<=a[t].y)//左区间的修改
//保证y有序 此时x不可能再有序了 插入BIT统计
{
//s小于等于 可能还有比t大的 s为t的答案提供贡献
//时间序在前 且位置在前(位置相同 先加后统计)
if(a[s].op==1)tr.add(a[s].x,a[s].w);
b[i]=a[s++];
}
else//右区间的查询 没有s.b<=t.b的了 统计t的答案
{
//在t位置前的 在该区间内都已被统计 故计算t的答案
if(a[t].op==2)ans[a[t].id]+=tr.sum(a[t].x);
else if(a[t].op==3)ans[a[t].id]-=tr.sum(a[t].x);
b[i]=a[t++];
}
}
//撤销 清空BIT操作
for(int i=l;i<=m;++i)
if(a[i].op==1)tr.add(a[i].x,-a[i].w);
for(int i=l;i<=r;++i)
a[i]=b[i];
}
int main()
{
scanf("%d",&t);
while(t--)
{
cnt=0;
scanf("%d%d%d",&n,&m,&p);
tr.init(n);
for(int i=1;i<=m;++i)
{
scanf("%d%d",&x,&y);
e[++cnt]=node{1,x,y,to(x,y),0};
}
for(int i=1;i<=p;++i)
{
ans[i]=0;
scanf("%d%d%d%d",&f,&g,&c,&d);
e[++cnt]=node{2,c,d,0,i};
e[++cnt]=node{2,f-1,g-1,0,i};
e[++cnt]=node{3,f-1,d,0,i};
e[++cnt]=node{3,c,g-1,0,i};
}
cdq(e,1,cnt);
for(int i=1;i<=p;++i)
printf("%d\n",ans[i]);
}
return 0;
}
B.super_log(欧拉降幂)
思路来源
https://www.cnblogs.com/ACMLCZH/p/8117161.html
求a的a的a的...次方(共b个)%m(1<=a<=1e6,0<=b<=1e6,1<=m<=1e6)
主办方出锅了,a不能等于1,log以1为底无意义一直递归
后来告知,a==1输出1%m
这题中在gcd(a,p)不等于1时,b可能小于phi[p],
所以与bzoj3884不同,详见cf906D的题解,
所以,学习了一种,在递归的过程中,
不固定加phi[mod],但在求快速幂的过程中多加mod的方法
因此,在调用快速幂过程中,应重写mod函数,整理为欧拉降幂的板子
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
typedef long long ll;
const int maxn=3e6+10;
bool ok[maxn];
ll prime[maxn],phi[maxn],cnt;
int T,p;
ll a,b,x;
void sieve()
{
phi[1]=1;
for(ll i=2;i<maxn;++i)
{
if(!ok[i])
{
prime[cnt++]=i;
phi[i]=i-1;
}
for(int j=0;j<cnt;++j)
{
if(i*prime[j]>=maxn)break;
ok[i*prime[j]]=1;
if(i%prime[j]==0)
{
phi[i*prime[j]]=phi[i]*prime[j];//prime[j]是i的因子 prime[j]的素因子项包含在i的素因子项里
break;
}
else phi[i*prime[j]]=phi[i]*(prime[j]-1);//prime[j]与i互质 phi[i*prime[j]=phi[i]*phi[prime[j]]
}
}
}
ll Mod(ll x,int mod)
{
return x<mod?x:x%mod+mod;
}
ll modpow(ll x,ll n,int mod)
{
ll ans=1;
for(;n;n/=2,x=Mod(x*x,mod))
if(n&1)ans=Mod(ans*x,mod);
return ans;
}
ll f(ll num,int mod)//要求a^a^a^a..(num个) %mod
{
if(mod==1)return 1;//a%mod+mod=1
if(num==1)return Mod(a,mod);
return modpow(a,f(num-1,phi[mod]),mod);
}
int main()
{
sieve();
scanf("%d",&T);
while(T--)
{
scanf("%lld%lld%d",&a,&b,&p);
if(a==1||b==0)
{
printf("%lld\n",1%p);
continue;
}
else if(b==1)
{
printf("%lld\n",a%p);
continue;
}
else
{
x=modpow(a,f(b-1,phi[p]),p);
printf("%lld\n",x%p);
}
}
return 0;
}
C.Tsy's number 5(化简式子+NTT)
题意

T(T<=5)组样例,每次给出一个n(n<=1e5),求上式mod998244353的值
题解
998244353大多和NTT有关,何况这种和式
先把i和j按phi值的贡献将其归位,按官方题解,

第二步化简,考虑i、j的矩阵表,完整矩阵=2*上三角矩阵-主对角线
第三步化简,把i*j搞成2*i*j,再搞成i j i-j独立的式子,
虽然只是完全平方的拆项,但放在NTT这里还是比较不好想,
对根号2做模意义下的二次剩余,for循环跑一下写到常数里
a[]:j序列,j从1起(但0无贡献,所以j从0起无妨),j*fj*根号2的j方次方
b[]:i-j序列,i-j从0起,根号2的0次方,-1次方,-4次方...
卷积只需要通过考虑j的范围,i-j的范围构造数组即可
直接相乘卷积,所得数组第i位,即为i的第二层答案
NTT预处理第二层求和,再O(n)一遍求和
代码
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <map>
using namespace std;
typedef long long ll;
const int maxn=1e5+10;
const int sqrt2=116195171;//x*x==2(mod 998244353) 2的二次剩余
bool ok[maxn];
int prime[maxn],phi[maxn],cnt;
int T,n,f[maxn],invsqrt2;
const int mod=998244353;
const int G=3;
ll inv,N;
ll ans,rev[maxn*3],back[maxn*3];
ll a[maxn*3],b[maxn*3],c[maxn*3],d[maxn*3];
void sieve()
{
phi[1]=1;
for(ll i=2;i<maxn;++i)
{
if(!ok[i])
{
prime[cnt++]=i;
phi[i]=i-1;
}
for(int j=0;j<cnt;++j)
{
if(i*prime[j]>=maxn)break;
ok[i*prime[j]]=1;
if(i%prime[j]==0)
{
phi[i*prime[j]]=phi[i]*prime[j];//prime[j]是i的因子 prime[j]的素因子项包含在i的素因子项里
break;
}
else phi[i*prime[j]]=phi[i]*(prime[j]-1);//prime[j]与i互质 phi[i*prime[j]=phi[i]*phi[prime[j]]
}
}
}
void getf(int n)
{
memset(f,0,sizeof f);
for(int i=1;i<=n;++i)
f[phi[i]]++;
}
ll power(ll x,ll y)
{
ll res=1ll;
for(;y;y>>=1)
{
if(y&1)res=res*x%mod;
x=x*x%mod;
}
return res;
}
void init(int n,int m)
{
int len=0;
while((n+m+2)>=(1<<len))len++;
N=(1<<len);
inv=power(N,mod-2);
for(int i=0;i<N;i++)
{
ll pos=0;
ll temp=i;
for(int j=1;j<=len;j++)
{
pos<<=1;pos |= temp&1;temp>>=1;
}
back[i]=rev[i]=pos;
}
}
void init2()
{
for(int i=0;i<N;++i)
rev[i]=back[i];
}
void ntt(ll *a,ll n,ll re)
{
for(int i=0;i<n;i++)
{
if(rev[i]>i)
{
swap(a[i],a[rev[i]]);
}
}
for(int i=2;i<=n;i<<=1)
{
ll mid=i>>1;
ll wn=power(G,(mod-1)/i);
if(re) wn=power(wn,(mod-2));
for(int j=0;j<n;j+=i)
{
ll w=1;
for(int k=0;k<mid;k++)
{
ll temp1=a[j+k];
ll temp2=a[j+k+mid]*w%mod;
a[j+k]=(temp1+temp2)%mod;
a[j+k+mid]=(temp1-temp2);
a[j+k+mid]=(a[j+k+mid]%mod+mod)%mod;
w=w*wn%mod;
}
}
}
if(re)
{
for(int i=0;i<n;i++)
{
a[i]=(a[i]*inv)%mod;
}
}
}
void solve2(ll len1,ll len2)
{
init2();
for(int i=0;i<N;++i)
a[i]=b[i]=0;
for(int i=0;i<len1;++i)
{
ll x=1ll*i*i,y=i*f[i]%mod;
a[i]=y*power(sqrt2,x)%mod;
c[i]=a[i];
d[i]=y*y%mod*power(2,x)%mod;
}
for(int i=0;i<len2;++i)
b[i]=power(invsqrt2,1ll*i*i);
ntt(a,N,0);
ntt(b,N,0);
for(int i=0;i<N;++i)
a[i]=a[i]*b[i]%mod;
ntt(a,N,1);
}
int main()
{
invsqrt2=power(sqrt2,mod-2);
sieve();
scanf("%d",&T);
while(T--)
{
scanf("%d",&n);
getf(n);
init(n+1,n+1);
solve2(n+1,n+1);
ans=0;
for(int i=1;i<=n;++i)
ans=(ans+2*c[i]*a[i]-d[i]+mod)%mod;
printf("%lld\n",ans);
}
return 0;
}
D. Robots(概率dp)
题意
n(n<=1e5)个点,m(m<=2e5)条边,保证是有向拓扑图,
只有一个点入度为0,一个点出度为0,
一个机器人从1点出发,每天要么转向一个邻接节点要么保持原地不动,所有可能都是等概率的
特别地,它这一天的cost等于已经经历过的天数
问从起点1到终点n的cost期望
题解
令t[i]代表i到n的期望时间,则,
其中,deg[i]是i的后继的个数,next[i]是i的后继,
也就是说无论走回自己还是走到其它顶点都需要额外花1天
预处理拓扑图,移项化简然后dp
令dp[i]代表i到n的期望代价,则,
为什么是t[i],可以倒着从n出发来考虑,从n到i需要t[i]天,
那么从i出发的时候已经经历了t[i]天,此时代价为t[i],
也就是说无论走回自己还是走到其它顶点都需要额外花t[i]的代价
dp[1]即为所求,即两次都倒着考虑
代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#include<queue>
using namespace std;
const int maxn=1e5+10;
int T,n,m,a,b,cnt,sum;
int in[maxn],c[maxn];
double t[maxn],dp[maxn];
//t[i]:i->n的期望时间
//dp[i]:i->n的期望代价
vector<int>e[maxn];
queue<int>q;
bool vis[maxn];
void topo()
{
for(int i=1;i<=n;++i)
{
if(!in[i])
{
q.push(i);
vis[i]=1;
}
}
while(!q.empty())
{
int t=q.front();
q.pop();
c[++cnt]=t;
int len=e[t].size();
for(int i=0;i<len;++i)
{
if((--in[e[t][i]])==0)
q.push(e[t][i]);
}
}
}
int main()
{
scanf("%d",&T);
while(T--)
{
scanf("%d%d",&n,&m);
cnt=0;
for(int i=1;i<=n;++i)
{
e[i].clear();
t[i]=dp[i]=in[i]=0;
}
for(int i=1;i<=m;++i)
{
scanf("%d%d",&a,&b);
e[a].push_back(b);in[b]++;
}
topo();
for(int i=n-1;i>=1;--i)
{
sum=e[c[i]].size()+1;
for(int j=0;j<e[c[i]].size();++j)
t[c[i]]+=t[e[c[i]][j]];
t[c[i]]=(t[c[i]]+sum)/(1.0*(sum-1));
}
for(int i=n-1;i>=1;--i)
{
sum=e[c[i]].size()+1;
for(int j=0;j<e[c[i]].size();++j)
dp[c[i]]+=dp[e[c[i]][j]];
dp[c[i]]=(dp[c[i]]+1.0*sum*t[c[i]])/(1.0*(sum-1));
}
printf("%.2lf\n",dp[1]);
}
return 0;
}
E.K Sum(杜教筛+莫比乌斯反演+欧拉定理+等比数列求和)
思路来源:https://blog.youkuaiyun.com/qq_30974369/article/details/79087445
题意
样例数T<=10,n<=1e9,k<=1e(1e5),
思路来源
https://blog.youkuaiyun.com/qq_30974369/article/details/79087445
题解
杜教筛 多自己手推推
先反演,弄出两个的时候的情况,
多个的情况显然就是等比数列求和,注意特判公比为1的情形,
化简不动的时候,注意枚举i*d=S中的S,
如果能快速地求图片中g(n),外层数论分块的复杂度就是
而g(n)显然积性函数,可以通过前面2e6用积性函数线性筛,
后面用杜教筛来完成,注意到g=(id*id)卷积mu,
g卷积1=(id*id)卷积mu卷积1=(id*id)卷积e=id*id
故,
其实,也就是mu常配1而已,但不要被套路套死,
代入g(1)=1即可,余则用杜教筛完成,
考虑到分块要求的[l,r]=djs(r)-djs(l-1),而杜教筛也是分块实现的,
二者分块的是相同的端点,所以求所有端点的复杂度,
相当于只求一遍n的杜教筛,复杂度只有而不是
感谢cometoj群 数论只会骗子 大佬耐心详细的解答 右下角那个分母是6

代码
复杂度由于杜教筛和分块的端点重合,所以杜教筛的复杂度只有,
分块套快速幂,总复杂度
#include<iostream>
#include<cstdio>
#include<cstring>
#include<map>
using namespace std;
typedef long long ll;
const int mod=1e9+7;
const int maxn=1e7+10;
const int inv6=(mod+1)/6;
const int N=1e5+10;
bool ok[maxn];
int prime[maxn],cnt;
int low[maxn],lowp[maxn];
ll f[maxn];
int T,n,len,x;
ll res,v;
char s[N];
map<int,ll>ff;
void sieve()
{
f[1]=1;
for(ll i=2;i<maxn;++i)
{
if(!ok[i])
{
prime[cnt++]=i;
low[i] = lowp[i] = i;
f[i] =(i*i-1)%mod;/*i为质数的情况f(p)*/
}
for(int j=0;j<cnt;++j)
{
ll k=i*prime[j];
if(k>=maxn)break;
ok[k]=1;
if(i%prime[j]==0)//i中出现过prime[j]
{
low[k]=prime[j];
lowp[k]=lowp[i]*prime[j];
if(i==lowp[i])f[k]=f[i]*(f[prime[j]]+1)%mod;/*i中全为prime[j] f(p^k)的情况*/
else f[k]=f[i/lowp[i]]*f[lowp[i]*prime[j]]%mod;/*i中不全为prime[j] 将最小素因子prime[j]和其他分开 显然互质*/
break;
}
else
{
low[k]=lowp[k]=prime[j];
f[k]=f[i]*f[prime[j]]%mod;//i中没出现过prime[j] i与prime[j]互质
}
}
}
for(int i=2;i<maxn;++i)
{
f[i]=(f[i]+f[i-1])%mod;
}
}
ll modpow(ll x,ll n,ll mod)
{
ll res=1;
for(;n;n/=2,x=x*x%mod)
if(n&1)res=res*x%mod;
return res;
}
ll cal(ll x)
{
ll inv=modpow(x-1,mod-2,mod);
ll y=modpow(x,v,mod);
ll z=modpow(x,2,mod);
return (y-z+mod)%mod*inv%mod;
}
ll getf(int n)
{
if(n<maxn)return f[n];
if(ff.count(n))return ff[n];
ll ans=1ll*n*(n+1)%mod*(2*n+1)%mod*inv6%mod;
for(int l=2,r;l<=n;l=r+1)
{
r=n/(n/l);
ans=(ans+mod-1ll*(r-l+1)*getf(n/l)%mod)%mod;
}
return ff[n]=ans;
}
ll solve(int n)
{
ll ans=0;
for(int l=1,r;l<=n;l=r+1)
{
r=n/(n/l);
if(r==n)ans=(ans+(getf(r)-getf(l-1)+mod)%mod*(res-1+mod)%mod)%mod;
else ans=(ans+(getf(r)-getf(l-1)+mod)%mod*cal(n/l)%mod)%mod;
}
return ans;
}
int main()
{
sieve();
scanf("%d",&T);
while(T--)
{
scanf("%d%s",&n,s);
len=strlen(s);
res=0;v=0;
for(int i=0;i<len;++i)
{
res=(res*10+(s[i]-'0'))%mod;
v=(v*10+(s[i]-'0'))%(mod-1);
}
v=(v+1)%(mod-1);
printf("%lld\n",solve(n));
}
return 0;
}
竞赛编程技巧与算法解析

本文深入探讨了网络赛中的数论难题,分享了在HolyGrail和GreedySequence问题上的解题心得,同时提供了详尽的代码示例。文章还讲解了Thebeautifulvaluesofthepalace、super_log、Tsy'snumber5、Robots和KSum等题目的解题思路,涵盖了树状数组、CDQ分治、欧拉降幂、NTT、概率DP、杜教筛等多种高级算法。

451

被折叠的 条评论
为什么被折叠?



