AtCoder赛记
(持续更新)
NIKKEI Programming Contest 2019-2
A:
送分。
#include<cstdio> int main(){ int n; scanf("%d",&n); printf("%d\n",n-1>>1); return 0; }
B:
想象对树分层即可。
#include<cstdio> typedef long long ll; const int mod=998244353; int cnt[100050]; inline int fp(int a,int p){ int s=1; while(p){ if(p&1)s=(ll)s*a%mod; a=(ll)a*a%mod; p>>=1; } return s; } int main(){ int n,i,x,maxn=0,ans=1; scanf("%d",&n); for(i=0;i<n;++i){ scanf("%d",&x); if(!i&&x){ puts("0"); return 0; } if(x>maxn)maxn=x; ++cnt[x]; } if(cnt[0]!=1){ puts("0"); return 0; } for(i=2;i<=maxn;++i)ans=(ll)ans*fp(cnt[i-1],cnt[i])%mod; printf("%d\n",ans); return 0; }
C:
对a,b排序,若存在$a_i>b_i$,则答案为No。若存在$a_{i+1}\le b_i$,则为Yes(因为给a排序最多只需要n-1次交换,现在可以少交换1次)。
否则,检查是否一定需要n-1次交换,这个可以通过检查置换循环节实现。
#include<cstdio> #include<algorithm> using namespace std; const int N=100050; struct node{ int a,b; }a[N]; int r[N]; inline bool cmp1(const node &a,const node &b){return a.b<b.b;} inline bool cmp2(int x,int y){return a[x].a<a[y].a;} int main(){ int n,i,p,tot=1; scanf("%d",&n); for(i=1;i<=n;++i){scanf("%d",&a[i].a);r[i]=i;} for(i=1;i<=n;++i)scanf("%d",&a[i].b); sort(a+1,a+n+1,cmp1);sort(r+1,r+n+1,cmp2); for(i=1;i<=n;++i)if(a[r[i]].a>a[i].b){ puts("No"); return 0; } for(i=1;i<n;++i)if(a[r[i+1]].a<=a[i].b){ puts("Yes"); return 0; } for(p=r[1];p!=1;p=r[p])++tot; puts(tot==n?"No":"Yes"); return 0; }
D:
线段树优化建图板子题。
#include<cstdio> #include<cstring> #include<queue> using namespace std; typedef long long ll; const int N=100050; int G[N*10],to[N*100],w[N*100],nxt[N*100],sz=0,tot,id1[N<<2],id2[N<<2],x,y,p,c; ll d[N*10]; struct node{ int p; ll d; node(){} node(int p,ll d):p(p),d(d){} inline bool operator <(const node &b)const{return d>b.d;} }; priority_queue<node> Q; inline void adde(int u,int v,int c){ to[++sz]=v;w[sz]=c;nxt[sz]=G[u];G[u]=sz; } void build(int o,int L,int R){ if(L==R){adde(L,id1[o]=++tot,0);adde(id2[o]=++tot,L,0);} else{ int lc=o<<1,rc=lc|1,M=L+R>>1; build(lc,L,M);build(rc,M+1,R); adde(id1[lc],id1[o]=++tot,0);adde(id1[rc],id1[o],0); adde(id2[o]=++tot,id2[lc],0);adde(id2[o],id2[rc],0); } } void ask(int o,int L,int R){ if(x<=L&&y>=R){adde(id1[o],p,c);adde(p,id2[o],0);} else{ int lc=o<<1,rc=lc|1,M=L+R>>1; if(x<=M)ask(lc,L,M); if(y>M)ask(rc,M+1,R); } } int main(){ int n,m,i,u,v; node h; scanf("%d%d",&n,&m); build(1,1,tot=n); while(m--){ scanf("%d%d%d",&x,&y,&c);p=++tot; ask(1,1,n); } memset(d,0x3f,sizeof(d)); Q.push(node(1,d[1]=0ll)); while(!Q.empty()){ h=Q.top();Q.pop(); if(d[u=h.p]!=h.d)continue; if(u==n){ printf("%lld\n",d[n]); return 0; } for(i=G[u];i;i=nxt[i])if(d[u]+w[i]<d[v=to[i]])Q.push(node(v,d[v]=d[u]+w[i])); } puts("-1"); return 0; }
AGC 039
A:
除了边上相连的以外都是不变的,分类讨论即可。
#include<cstdio> #include<cstring> char s[105]; int len[105]; int main(){ int n,k,i,cnt=1; long long ans=0ll; scanf("%s%d",s+1,&k);n=strlen(s+1); len[1]=1; for(i=2;i<=n;++i)if(s[i]!=s[i-1])len[++cnt]=1; else ++len[cnt]; if(cnt==1){ printf("%lld\n",1ll*n*k>>1ll); return 0; } for(i=2;i<cnt;++i)ans+=len[i]>>1; ans*=k; if(s[n]==s[1])ans+=1ll*(len[1]+len[cnt]>>1)*(k-1); else ans+=1ll*((len[1]>>1)+(len[cnt]>>1))*(k-1); printf("%lld\n",ans+(len[1]>>1)+(len[cnt]>>1)); return 0; }
B:
一定有一个点在一号集里,枚举这个点,就可以推出其余点。
#include<cstdio> #include<cstring> #include<queue> using namespace std; char s[205]; bool G[205][205]; int d[205],n; queue<int> Q; inline bool bfs(int u){ int v; while(!Q.empty())Q.pop(); d[u]=1;Q.push(u); while(!Q.empty()){ u=Q.front();Q.pop(); for(v=1;v<=n;++v)if(G[u][v])if(!d[v]){ d[v]=d[u]+1; Q.push(v); }else if(d[v]!=d[u]+1&&d[v]!=d[u]-1)return 0; } return 1; } int main(){ int i,j,ans=-1; scanf("%d",&n); for(i=1;i<=n;++i){ scanf("%s",s+1); for(j=1;j<=n;++j)G[i][j]=s[j]=='1'; } for(i=1;i<=n;++i){ memset(d,0,sizeof(d)); if(bfs(i))for(j=1;j<=n;++j)if(d[j]>ans)ans=d[j]; } printf("%d\n",ans); return 0; }
C:
首先所有串一定可以用2n次操作变回原样,因此只要寻找特殊串即可。
若记把$T$二进制取反后的串为$T'$,那么所有特殊串都可表示成为$TT'TT'\cdots TT'T$的形式,且操作次数为$2len_T$,枚举len,循环节比X前len位小的必然可行,大的必然不行,相等的构造出来暴力比,len顶多$O(\sqrt{n})$个(实际上远远达不到),可以AC。
注意len=9的种数可能会包含len=3的种数,所以还要减一下。
#include<cstdio> typedef long long ll; const int mod=998244353; char s[200050]; bool a[200050],b[200050]; int n,ans=0,tmp[200050],res[200050]; inline bool cmp(){ for(int i=1;i<=n;++i)if(a[i]!=b[i])return a[i]<b[i]; return 1; } int main(){ int i,j,k; scanf("%d%s",&n,s+1); for(i=1;i<=n;++i){ b[i]=s[i]=='1'; tmp[i]=((tmp[i-1]<<1)+b[i])%mod; } ans=(ll)(tmp[n]+1)*n*2%mod; for(k=1;k<=n/3;++k)if(!(n%k)&&n/k%2==1){ res[k]=tmp[k]; for(i=1;i<=k;++i)a[i]=b[i]; for(;i<=n;i+=k) for(j=0;j<k;++j)a[i+j]=a[i+j-k]^1; if(cmp())res[k]=(res[k]+1)%mod; if(k>1)for(i=1;i*i<=k;++i)if(!(k%i)){ res[k]=(res[k]-res[i]+mod)%mod; if(i*i!=k&&i!=1)res[k]=(res[k]-res[k/i]+mod)%mod; } ans=(ans-(ll)res[k]*(n-k<<1)%mod+mod)%mod; } printf("%d\n",ans); return 0; }
AGC 038
A:
脑筋急转弯?
#include<cstdio> bool A[1005][1005]; int main(){ int n,m,a,b,i,j; scanf("%d%d%d%d",&n,&m,&b,&a); for(i=1;i<=a;++i) for(j=1;j<=b;++j)A[i][j]=1; for(i=a+1;i<=n;++i) for(j=b+1;j<=m;++j)A[i][j]=1; for(i=1;i<=n;++i){ for(j=1;j<=m;++j)printf("%d",A[i][j]); putchar('\n'); } return 0; }
B:
两个相邻位置$i,i+1$执行操作后结果相同要求$a_i\le a_{i+1,i+2,\cdots,i+k}$且$a_{i+k}\ge a_{i,i+1,\cdots,i+k-1}$,可以单调队列维护,注意特判。
#include<cstdio> int a[200050],b[200050]; struct que1{ int q[200050],h,r; inline void init(){ h=1;r=0; } inline void push(int x){ while(h<=r&&x>q[r])--r; q[++r]=x; } inline void pop(int x){if(x==q[h])++h;} }Q; struct que2{ int q[200050],h,r; inline void init(){ h=1;r=0; } inline void push(int x){ while(h<=r&&x<q[r])--r; q[++r]=x; } inline void pop(int x){if(x==q[h])++h;} }q; int main(){ int n,k,i,cnt=1,ans=1; bool f; scanf("%d%d",&n,&k); for(i=1;i<=n;++i)scanf("%d",a+i); for(b[1]=1,i=2;i<=n;++i)if(a[i]>a[i-1])b[i]=b[i-1]; else b[i]=++cnt; Q.init();q.init(); for(i=1;i<=k;++i)Q.push(a[i]),q.push(a[i]); f=b[k]==b[1]; for(;i<=n;++i){ Q.pop(a[i-k]);q.pop(a[i-k]); if(q.q[q.h]<a[i-k]||a[i]<Q.q[Q.h])if(b[i]==b[i-k+1])if(!f){++ans;f=1;} else; else ++ans; Q.push(a[i]);q.push(a[i]); } printf("%d\n",ans); return 0; }
C:
首先$lcm(a,b)=\frac{ab}{gcd(a,b)}$,再构造数列$w$满足$\sum\limits_{d|n}w_d=\frac{1}{n}$(这个可以用一个个减的方法得到),那么$ans=\sum\limits_d\sum\limits_{d|a_i}\sum\limits_{i<j,d|a_j}w_da_ia_j$,可以枚举$d$后算出。
#include<cstdio> typedef long long ll; const int mod=998244353; int w[1000050],cnt[1000050]; int main(){ int n,m=0,i,j,x,sum,ans=0; scanf("%d",&n); for(i=0;i<n;++i){ scanf("%d",&x); if(x>m)m=x; ++cnt[x]; } for(i=2,w[1]=1;i<=m;++i)w[i]=(ll)(mod-mod/i)*w[mod%i]%mod; for(i=1;i<=(m>>1);++i) for(j=(i<<1);j<=m;j+=i){w[j]-=w[i];if(w[j]<0)w[j]+=mod;} for(i=1;i<=m;++i){ for(j=i,sum=0;j<=m;j+=i){ ans=(ans+((ll)sum*j%mod*cnt[j]+((ll)cnt[j]*(cnt[j]-1)>>1ll)%mod*j%mod*j)%mod*w[i])%mod; sum=(sum+(ll)j*cnt[j])%mod; } } printf("%d\n",ans); return 0; }
Japanese Student Championship 2019 Qualification
A:
送分题,$O(md)$暴力即可。
#include<cstdio> int main(){ int m,d,i,j,ans=0; scanf("%d%d",&m,&d); for(i=1;i<=m;++i) for(j=22;j<=d;++j)if(j/10>=2&&j%10>=2&&(j/10)*(j%10)==i)++ans; //22以前的日期十位必然<2 printf("%d\n",ans); return 0; }
B:
把一个序列复制k份,首先序列内逆序对数会乘k,而对于跨序列的情况,对于第$i$个序列里的一个数$a$,它额外的逆序对数是$\text{序列内比a大的数的个数}\times (i-1)$,所以所有$a$的总贡献就是$\text{序列内比a大的数的个数}\times\frac{k(k-1)}{2}$。因为$n\le 2000$很小,直接$O(n^2)$暴力求就好了。
注意第三个样例是10 9 8 7 5 6 3 4 2 1
#include<cstdio> typedef long long ll; const int mod=1000000007; int a[2005]; int main(){ int n,k,i,j,res,ans=0; scanf("%d%d",&n,&k); for(i=1;i<=n;++i)scanf("%d",a+i); for(i=2;i<=n;++i) for(j=1;j<i;++j)if(a[j]>a[i])++ans; ans=(ll)ans*k%mod; for(i=1;i<=n;++i){ for(j=1,res=0;j<=n;++j)if(a[j]>a[i])++res; ans=(ans+(ll)res*k%mod*(k-1)%mod*500000004)%mod; } printf("%d\n",ans); return 0; }
C:
应该发现的一个事实是$(l_1,r_1),(l_2,r_2)$和$(l_1,r_2),(l_2,r_1)$产生的效果是相同的。
另一个结论比较难发现:每个位置要么作为l,要么作为r,不存在某个位置既能作l又能作r。
因为最左边显然为l,而对于WW或BB的情况,前后两个不能均为l或均为r;同时,对于BW或WB的情况,前后两个必须均为l或r(否则将无法把它们都变成W)。
若最左边或最右边为W(它们不能被改回来),或l,r位置数目不等则无解,否则把l,r适当配对即可,扫描线一下就可以得出方案数,复杂度$O(n)$。
#include<cstdio> typedef long long ll; const int mod=1000000007; char s[200050]; bool d[200050]; int sum[200050]; int main(){ int n,i,ans=1,cnt=0; scanf("%d%s",&n,s+1); if(s[1]=='W'||s[n<<1]=='W'){ puts("0"); return 0; } for(i=2,d[1]=0,sum[1]=1;i<=(n<<1);++i)sum[i]=sum[i-1]+!(d[i]=d[i-1]^(s[i]==s[i-1])); if(sum[n<<1]!=n){ puts("0"); return 0; } for(i=2;i<=(n<<1);++i)if(d[i]){ ans=(ll)ans*(sum[i-1]-cnt)%mod; ++cnt; } for(i=2;i<=n;++i)ans=(ll)ans*i%mod; printf("%d\n",ans); return 0; }
D:
转化题意:已知一个n个结点的完全图,请用尽量少的颜色将边染色,使图上不存在同色的奇环。
一个很好想的思路是按照$|u-v|$的奇偶性连边,但这样连是错误的:(1,3),(3,5),(1,5)均为偶边。所以想到(1,5)要用第三种颜色,由此想到根据$|u-v|$的二进制最低位连边。
为什么这样是正确的呢?考虑一个点$u$,如果从它出发有同色奇环,则从它出发,沿偶数条同色边可以走到一个与它直接用这一色边相连的结点,而我们知道$u+2^i\times 2k=u+2^{i+1}\times k$,即从$u$出发后走偶数条$i$色边后到达的点和它连边的颜色至少为$i+1$,故奇环不存在。
复杂度$O(n^2logn)$。
#include<cstdio> inline int getit(int x){ int t=x&-x,res=0; while(t){t>>=1;++res;} return res; } int main(){ int n,i,j; scanf("%d",&n); for(i=1;i<n;++i){ for(j=i+1;j<=n;++j)printf("%d ",getit(j-i)); putchar('\n'); } return 0; }
AGC 037
A:
没想到dpQAQ。考虑贪心,将$s$分解为若干个只含相同字符的串的链接,记每个串内有$cnt_i$个字符。发现$cnt_i\;mod\;3=2$时,拼上一个才有利;$cnt_i\;mod\;3=2$时,拆一个往后拼必赚;$cnt_i\;mod\;3=1$时,拆一个往后拼必亏;$cnt_i\;mod\;3=0$时,拆一个往后拼不赚不亏。但是考虑下面的例子:
aabbbaa
最优解是将中间的bbb拆两个分给两边,所以$cnt_i\;mod\;3=0$时也要拆掉。
复杂度$O(n)$。
#include<cstdio> #include<cstring> char s[200050],c[200050]; int cnt[200050]; int main(){ int n,m=1,i,ans=0; scanf("%s",s);n=strlen(s); c[1]=s[0];cnt[1]=1; for(i=1;i<n;++i)if(s[i]!=c[m]){c[++m]=s[i];cnt[m]=1;} else ++cnt[m]; for(i=m-1;i;--i)if((cnt[i]%3==0||cnt[i]%3==2)&&cnt[i+1]%3==2){ --cnt[i];--cnt[i+1]; ++ans; } for(i=1;i<=m;++i)ans+=cnt[i]/3*2+(cnt[i]%3!=0); printf("%d\n",ans); return 0; }
B:
推错式子导致白费工夫+没做出,于是掉rating了。考虑记第$i$个$R,G,B$出现的位置为$r_i,g_i,b_i$,记$A_i=\min(r_i,g_i,b_i),C_i=\max(r_i,g_i,b_i),B_i=\text{剩下的那个}$,则最小贡献为$\sum\limits^n_{i=1}(C_i-A_i)$,方案数就是把它们配对满足$A_j<B_j<C_j$的方案数。最后乘上$n!$。
复杂度$O(n)$。
#include<cstdio> #include<algorithm> #include<vector> using namespace std; typedef long long ll; const int mod=998244353; char s[300050]; int A[100050],B[100050],C[100050]; vector<int> a,b,c; int main(){ int n,i,x,ans=1; scanf("%d%s",&n,s+1); for(i=1;i<=n*3;++i)if(s[i]=='R')a.push_back(i); else if(s[i]=='G')b.push_back(i); else c.push_back(i); for(i=0;i<n;++i)if(a[i]<b[i])if(a[i]<c[i]){ A[i+1]=a[i]; if(b[i]<c[i]){B[i+1]=b[i];C[i+1]=c[i];} else{B[i+1]=c[i];C[i+1]=b[i];} }else{A[i+1]=c[i];B[i+1]=a[i];C[i+1]=b[i];} else if(b[i]<c[i]){ A[i+1]=b[i]; if(a[i]<c[i]){B[i+1]=a[i];C[i+1]=c[i];} else{B[i+1]=c[i];C[i+1]=a[i];} }else{A[i+1]=c[i];B[i+1]=b[i];C[i+1]=a[i];} for(i=1;i<=n;++i){ x=upper_bound(A+1,A+n+1,B[i])-A-1; ans=(ll)ans*(x-i+1)%mod*i%mod; } for(i=n;i;--i){ x=lower_bound(C+1,C+n+1,B[i])-C; ans=(ll)ans*(i-x+1)%mod; } printf("%d\n",ans); return 0; }
C:
对于终止状态,考虑最后得到的那个数,它肯定是加上两边的数得到的,而两边的数都是确定的!所以倒退,看是否能回到原始状态即可。
实现时,找出所有满足$b_i>a_i$且$b_i-a_i\ge b_{i-1}+b_{i+1}$的结点,很明显这些点不会相邻,所以答案与选择顺序无关,同时每次修改只会影响两侧的点,队列/栈模拟,复杂度$O(nloga_i)$。
#include<cstdio> #include<queue> using namespace std; int f[200050],a[200050],n; bool inq[200050]; inline int lst(int x){return x-1==0?n:x-1;} inline int nxt(int x){return x+1>n?1:x+1;} struct node{ int p; node(){} node(int p):p(p){} }; queue<node> Q; inline bool check(int i){return a[i]>f[i]&&a[i]-f[i]>=a[lst(i)]+a[nxt(i)];} int main(){ int i,t1,t2; long long ans=0ll; scanf("%d",&n); for(i=1;i<=n;++i)scanf("%d",f+i); for(i=1;i<=n;++i){ scanf("%d",a+i); if(a[i]<f[i]){ puts("-1"); return 0; } } for(i=1;i<=n;++i)if(check(i))Q.push(node(i)),inq[i]=1; while(!Q.empty()){ i=Q.front().p;Q.pop();inq[i]=0; t1=a[i]-f[i]; t2=a[lst(i)]+a[nxt(i)]; ans+=t1/t2; a[i]=f[i]+t1%t2; if(!inq[lst(i)]&&check(lst(i)))Q.push(lst(i)),inq[lst(i)]=1; if(!inq[nxt(i)]&&check(nxt(i)))Q.push(nxt(i)),inq[nxt(i)]=1; } for(i=1;i<=n;++i)if(f[i]!=a[i]){ puts("-1"); return 0; } printf("%lld\n",ans); return 0; }
D:
要让第3步之后排完,第3步之前肯定各行的数集是$(1,2,\cdots,m),(m+1,m+2,\cdots,2m),\cdots,((n-1)m+1,(n-1)m+2,\cdots,nm)$。所以第二步开始时各列的$n$个数应恰好分别属于$n$个集,以列和集为左/右点建二分图,问题转化为把$nm$条边组织成$m$个完美匹配,Dinic复杂度$O(nm^2\sqrt{n})$。
#include<cstdio> #include<cstring> #include<algorithm> #include<queue> using namespace std; int a[105][105],b[105][105],G[205],to[20405],cap[20405],nxt[20405],sz=-1,d[205],cur[205],id[105][105],n,tmp[105]; bool used[105][105]; queue<int> Q; inline void adde(int u,int v,int c){ to[++sz]=v;cap[sz]=c;nxt[sz]=G[u];G[u]=sz; to[++sz]=u;cap[sz]=0;nxt[sz]=G[v];G[v]=sz; } inline bool bfs(){ int i,u,v; memset(d,0x3f,sizeof(d)); d[0]=0;Q.push(0); while(!Q.empty()){ u=Q.front();Q.pop(); for(i=G[u];i!=-1;i=nxt[i])if(cap[i]&&d[v=to[i]]==0x3f3f3f3f){ d[v]=d[u]+1; Q.push(v); } } return d[n<<1|1]!=0x3f3f3f3f; } int dfs(int u,int a){ if(u==(n<<1|1)||!a)return a; int v,flow=0,f; for(int &i=cur[u];i!=-1;i=nxt[i])if(d[v=to[i]]==d[u]+1&&(f=dfs(v,min(a,cap[i])))){ flow+=f; cap[i]-=f;cap[i^1]+=f; if(!(a-=f))break; } return flow; } inline void dinic(){ while(bfs()){ memcpy(cur,G,sizeof(cur)); dfs(0,0x3f3f3f3f); } } int main(){ int m,i,j,k; scanf("%d%d",&n,&m); memset(G,-1,sizeof(G)); for(i=1;i<=n;++i){adde(0,i,0);adde(i+n,n<<1|1,0);} for(i=1;i<=n;++i) for(j=1;j<=m;++j){ scanf("%d",&a[i][j]); id[i][j]=sz+1; adde(i,(a[i][j]+m-1)/m+n,1); } for(i=1;i<=m;++i){ for(j=0;j<(n<<2);j+=4){++cap[j];++cap[j+2];} dinic(); for(j=1;j<=n;++j) for(k=1;k<=m;++k)if(!used[j][k]&&!cap[id[j][k]]){ b[j][i]=a[j][k]; used[j][k]=1; cap[id[j][k]^1]=0; } } for(i=1;i<=n;++i){ for(j=1;j<=m;++j)printf("%d ",b[i][j]); putchar('\n'); } for(j=1;j<=m;++j){ for(i=1;i<=n;++i)tmp[i]=b[i][j]; sort(tmp+1,tmp+n+1); for(i=1;i<=n;++i)b[i][j]=tmp[i]; } for(i=1;i<=n;++i){ for(j=1;j<=m;++j)printf("%d ",b[i][j]); putchar('\n'); } return 0; }