===A=== ===B=== 瞎搞题 **AC代码** #include #define ll long long using namespace std; const int maxn=1e6+5; ll x[maxn],y[maxn]; int n; ll getS(int a[]){ return abs(x[a[0]]*y[a[1]]-x[a[0]]*y[a[2]]+x[a[1]]*y[a[2]]-x[a[1]]*y[a[0]]+x[a[2]]*y[a[0]]-x[a[2]]*y[a[1]]); } int main(){ scanf("%d",&n); for(int i=0;i ===C=== 线段树 **AC代码** #include #define ll long long using namespace std; const int maxn=1e6+50; ll n,m,k; ll lazy[maxn]; ll minn[maxn]; struct operation{ ll t,x; int g; operation(ll a,ll b,int c){ t=a; x=b; g=c; } }; vectorop[maxn]; void pushdown(int rt){ if(lazy[rt]){ lazy[rt<<1]+=lazy[rt]; lazy[rt<<1|1]+=lazy[rt]; minn[rt<<1]+=lazy[rt]; minn[rt<<1|1]+=lazy[rt]; lazy[rt]=0; } } void pushup(int rt){ minn[rt]=min(minn[rt<<1],minn[rt<<1|1]); } void update(int l,int r,int L,int R,ll v,int rt){ if(r<=R&&l>=L){ minn[rt]+=v; lazy[rt]+=v; return; } pushdown(rt); int mid=(l+r)>>1; if(L<=mid)update(l,mid,L,R,v,rt<<1); if(R>mid)update(mid+1,r,L,R,v,rt<<1|1); pushup(rt); } ll query(int l,int r,int L,int R,int rt){ if(r<=R&&l>=L)return minn[rt]; pushdown(rt); int mid=(l+r)>>1; ll res=1e9; if(L<=mid)res=min(res,query(l,mid,L,R,rt<<1)); else if(R>mid)res=min(res,query(mid+1,r,L,R,rt<<1|1)); return res; } int main(){ cin>>n>>m>>k; int o,a,b; ll c; for(int i=1;i<=m;i++){ cin>>o; if(o==1){ cin>>a>>b>>c; op[a].push_back(operation(i,c,0)); op[b+1].push_back(operation(i,-c,0)); }else{ cin>>a>>b; op[a].push_back(operation(i,-k,1)); op[b+1].push_back(operation(i,k,-1)); } } ll ans=0,tmp=0,num=0; for(int i=1;i<=n;i++){ int sz=op[i].size(); for(int j=0;j0){ tmp=num-ceil(-1.0*min(minn[1],0ll)/k); } ans+=tmp; } cout< ===D=== 签到题 **题目大意** 给定一个n*m的矩阵,两人博弈,每次可以选择一个i<=n,j<=m,所有在i*j矩阵区域内的点都会被访问,每次选择的i,j必须保证新访问一个未被访问过的点,必须要访问最后一个没被访问的点的人输。 **算法思路** 考虑在n=1,m=1时一定是后手赢,一行一列的时候一定是先手赢 对于更广泛的情况,考虑维护L型,无论后手如何操作,先手一定能维护L型,那么最终后手一定会走到一行一列的状态,因此L型是必败态 **AC代码** #include using namespace std; int main(){ int n,m; cin>>n>>m; if(n==1&&m==1) printf("Walk Alone"); else printf("Kelin"); } ===E=== ===F=== ===G=== ===H=== **题目大意** 给定两个长度为n的数组**a**,**b**,记d=sum(abs(a[i]-b[i])),在至多交换数组b中的两个元素一次的情况下求d的最小值。 **算法思路** 设不交换时的答案为d,若交换b[i]和b[j],则最终答案变为d-2*(min(max(a[i],b[i]),max(a[j],b[j]))-max(min(a[i],b[i]),min(a[j],b[j]))) 目标是找到i,j使上式被减数最大。 考虑尺取法,将max(a[i],b[i])记为maxx[i],并且将a[i]>b[i]和a[i]<=b[i]的a[i]和b[i]分别存在两个数组里,记为**p[0]**和**p[1]** p[0],p[1]按照较大数从大到小排序,maxx按照从大到小排列。 从大到小遍历maxx数组,找到当p[0],p[1]的较大数>=maxx[i]时,较小数的最小值min0,min1,并且在之前遍历时所计算得到的最小值依然满足要求,可以直接使用,最后计算maxx[i]-max(min0,min1)取最大值 遍历结束后会求出一个最大的maxx[i]-max(min0,min1),为所求,输出ans-2*(maxx[i]-max(min0,min1)); 写的思路略乱,看代码比较清晰 **AC代码** #include #define ll long long using namespace std; const int maxn=1e6+5; ll a[maxn],b[maxn],n; ll ans; pairpa[2][maxn]; ll maxx[maxn]; bool cmp(paira, pairb){ return a.first>n; for(int i=0;i>a[i]; } int cnt[2]={0,0}; for(int i=0;i>b[i]; ans+=abs(b[i]-a[i]); if(a[i]>b[i]){ pa[0][cnt[0]++]=make_pair(a[i],b[i]); }else{ pa[1][cnt[1]++]=make_pair(b[i],a[i]); } maxx[i]=max(a[i],b[i]); } sort(pa[0],pa[0]+cnt[0],cmp); sort(pa[1],pa[1]+cnt[1],cmp); sort(maxx,maxx+n); int p[2]={cnt[0]-1,cnt[1]-1}; ll d=0; ll mina=2e9; ll minb=2e9; for(int i=n-1;i>=0;i--){ while(p[0]>=0&&pa[0][p[0]].first>=maxx[i]){ mina=min(mina,pa[0][p[0]].second); p[0]--; } while(p[1]>=0&&pa[1][p[1]].first>=maxx[i]){ minb=min(minb,pa[1][p[1]].second); p[1]--; } d=max(d,maxx[i]-max(mina,minb)); } ans=ans-2*d; cout< ===I=== ===J=== **题目大意** 赌博,初始赌1块钱,如果输了,下次赌两倍(1,2,4...),如果赢了,获得当前次两倍的赌资(赢2,4,8...),如果现在的钱不够赌,则失败。 现在有n块钱,想赢到n+m块,求成功的概率。 **算法思路** 钱数为x时失败的概率为1/(2^k),k=[log2(x+1)] 设从n赢到x块钱时失败的概率为ans,则从n赢到x+1块钱时失败的概率为ans+(1-ans)*1/(2^k),k=[log2(x+2)] 对于一段区间内的x,其k是相同的,这是一个线性递推数列,很容易得到通项公式a_n=(ans-1)*(1-1/(2^k))^n+1,a_0=ans; 即ans=(ans-1)*(1-1/(2^k))^n+1 遍历每一个k,找到对应的项数n,更新ans,可求出最终失败的概率 输出1-ans即为答案 **AC代码** #include using namespace std; long long n,m; const int mod=998244353; long long mul(long long a,long long b){ long long res=1; while(b){ if(b&1){ res=res*a%mod; } a=a*a%mod; b>>=1; } return res; } int main(){ cin>>n>>m; long long tmp=n; long long ans=0; long long p=pow(2,int(log2(n+1))+1)-1; p=min(p,tmp+m); int a0=log2(n+1); int a1=log2(n+m); for(int i=a0;i<=a1;i++){ long long Ln=mul(pow(2,i),mod-2); long long C=(ans-1+mod)%mod; ans=(C*mul((1-Ln+mod)%mod,p-n)%mod+1)%mod; n=p; p=min(tmp+m,(p+1)*2-1); } cout<<(1-ans+mod)%mod< ===K=== **题目大意** 给定一个简单图和常数k,每条边长度均为1,可以在图上的任意一条边上加一个点,操作次数不限。求最终图中与1号节点距离不大于k的点最多有多少个。 **算法思路** bfs固定一棵生成树,同时处理处每个点到1号点的距离。可以在所有非生成树的边加满点,所有生成树边不动(非生成树的边好像叫桥来着) 遍历每一条非生成树边,可以加2*k-dis[u]-dis[v]数量的点,u,v为该边两端的节点。注意处理u或v本身距离大于k的情况。 最后检查生成树的叶节点,若叶节点的距离小于k,可以在叶子结点处加点k-dis[leaf] 答案为上述所添加的节点数 + 最初距离小于等于k的节点数 **AC代码** #include #define ll long long using namespace std; const int maxn=2e5+50; ll n,m,k,ans; int to[maxn<<1]; int nxt[maxn<<1]; int head[maxn],cnt=-1; int a,b,d[maxn]; int dis[maxn]; bool vis[maxn]; bool f[maxn<<1]; bool flag[maxn]; void add_edge(int u,int v){ to[++cnt]=v; nxt[cnt]=head[u]; head[u]=cnt; } vectorve[maxn]; void bfs(int st){ queue >qu; qu.push(make_pair(st,0)); vis[st]=1; while(!qu.empty()){ int fr=qu.front().first; int d=qu.front().second; qu.pop(); for(int i=head[fr];i!=-1;i=nxt[i]){ if(vis[to[i]]){ continue; } vis[to[i]]=1; qu.push(make_pair(to[i],d+1)); ve[fr].push_back(to[i]); f[i]=1; dis[to[i]]=d+1; } } } bool dfs(int p,int fa){ int sz=ve[p].size(); ans++; for(int i=0;ik)continue; if(!dfs(ve[p][i],p)){ ans+=k-dis[ve[p][i]]; } flag[p]=1; } for(int i=head[p];i!=-1;i=nxt[i]){ if(f[i]||to[i]==fa||dis[to[i]]>k)continue; ans+=2*k-dis[p]-dis[to[i]]; f[i]=1; f[i^1]=1; flag[p]=1; flag[to[i]]=1; } return flag[p]; } int main(){ cin>>n>>m>>k; memset(head,-1,sizeof(head)); for(int i=0;i>a>>b; add_edge(a,b); add_edge(b,a); } bfs(1); dfs(1,0); cout< ===L=== **题目大意** 给定三个长度为n的排列**a**,**b**,**c**和x,y,z。经过一次操作x,y,z变为a[y],b[z],c[x](注意下标顺序),初始x=y=z=1。 Q次询问,每次询问x',y',z',求最少的操作次数,使(x,y,z)=(1,1,1)变为(x',y',z'),如果不能则输出-1。 **算法思路** 只考虑x,每3次操作,x会变成a[b[c[x]]],维护px[i]为i经过3次操作变成了px[i],则px[i]也是n的一个排列。x按照x=px[x]的规则变换,该变换会在px中形成若干个环,我们预处理出每个x在px中参与的环长度cir[x]和在环中的位置dis[x],同时记录每个x所属环的编号。那么最终x到达x'的操作次数为dis[x']-dis[x]+k*cir[x]。对a,b,c三个排列都按如上方式处理。 查询时,只需求是否存在一组k1,k2,k3,使得dis[x']-dis[1]+k1*cir[x']=dis[y']-dis[1]+k2*cir[y']=dis[z']-dis[1]+k3*cir[z']. 即求同余方程组:(用扩展中国剩余定理) **T=(dis[x']-dis[1])%cir[x']** **T=(dis[y']-dis[1])%cir[y']** **T=(dis[z']-dis[1])%cir[z']** 由于该预处理方法是每三个一跳,遍历三组初始值x=1,y=1,z=1、x=a[1],y=b[1],z=c[1]、x=a[b[1]],y=b[c[1]],z=c[a[1]],每组求出一个T,最终每组的答案分别为3*T+i(i=0,1,2),取最小值。 无解情况:x'与x不在同一个环上,或方程组无解。 **AC代码** #include #define ll long long using namespace std; const int maxn=1e5+50; ll n,q; ll a[3][maxn]; bool f[3][maxn]; ll p[3][maxn]; ll dis[3][maxn]; ll cir[maxn]; int ma[3][maxn]; int cnt; int getstart(int u,int v,int id){ if(ma[id][u]!=ma[id][v])return -1; int tmp=dis[id][v]-dis[id][u]; return tmp<0?tmp+cir[ma[id][u]]:tmp; } ll mul(ll a,ll b,ll mod){ ll res=0; while(b){ if(b&1){ res=(res+a)%mod; } a=(a+a)%mod; b>>=1; } return res; } ll ex_gcd(ll A,ll B,ll &x,ll &y){ if(B==0){ x=1; y=0; return A; } ll d=ex_gcd(B,A%B,y,x); y-=A/B*x; return d; } int main(){ cin>>n; memset(dis,-1,sizeof(dis)); for(int i=0;i<3;i++){ for(int j=1;j<=n;j++){ cin>>a[i][j]; } } for(int i=1;i<=n;i++){ p[0][i]=a[0][a[1][a[2][i]]]; p[1][i]=a[1][a[2][a[0][i]]]; p[2][i]=a[2][a[0][a[1][i]]]; } for(int i=1;i<=n;i++){ for(int j=0;j<3;j++){ if(ma[j][i])continue; ma[j][i]=++cnt; dis[j][i]=0; int k=i,len=0; do{ ma[j][p[j][k]]=cnt; dis[j][p[j][k]]=dis[j][k]+1; k=p[j][k]; len++; }while(i!=k); cir[cnt]=len; } } cin>>q; ll x,y,z; while(q--){ cin>>x>>y>>z; ll ans=-1; for(int i=0;i<3;i++){ int x1=1,y1=1,z1=1; if(i==1){ x1=a[0][1]; y1=a[1][1]; z1=a[2][1]; } if(i==2){ x1=a[0][a[1][1]]; y1=a[1][a[2][1]]; z1=a[2][a[0][1]]; } int st[3]; st[0]=getstart(x1,x,0); st[1]=getstart(y1,y,1); st[2]=getstart(z1,z,2); int c[3]; c[0]=cir[ma[0][x]]; c[1]=cir[ma[1][y]]; c[2]=cir[ma[2][z]]; //solve the equations by using ex_CRT //t=st[0] (mod c[0]) //t=st[1] (mod c[1]) //t=st[2] (mod c[2]) if(st[0]<0||st[1]<0||st[2]<0)continue; ll m=c[0]; ll tmp=st[0]; for(int i=1;i<3;i++){ //solve the equation: //tmp + X*m = st[i] (mod c[i]) //X*m + Y*c[i] = st[i]-tmp //d = gcd(m,c[i]); //-->X*(m/d) + Y*(c[i]/d) = (st[i]-tmp)/d ll X,Y; ll d=ex_gcd(m,c[i],X,Y); if((tmp-st[i])%d!=0){ tmp=-1; break; } //find the minimum solution:X = X * (st[i]-tmp)/d % (c[i]/d); X=mul(X,((st[i]-tmp%c[i]+c[i])%c[i])/d,c[i]/d); tmp=tmp+X*m; m=m*c[i]/d; tmp=(tmp%m+m)%m; } if(tmp==-1)continue; if(ans==-1)ans=tmp*3+i; else ans=min(ans,tmp*3+i); } cout< ===M=== =====知识点总结===== ====扩展中国剩余定理==== 解同余方程组 x=r[i](mod m[i]),i=0,1,...,n-1 注意模数m可能不两两互质 遍历i=1,2,3...,n-1,每次解方程x*+t*M=r[i](mod m[i]),即t*M+k*m[i]=r[i]-x*,其中x*为之前i-1个方程的一个解,M为前i-1个m的最小公倍数 最后解为x*+k*lcm(m[i]) **模板(缩略版)** ll m[n],r[n]; ll ex_CRT(){ ll M=m[0]; ll tmp=r[0]; for(int i=1;i