(持续更新)
NIKKEI Programming Contest 2019-2
A:
送分。

#includeint main(){ int n; scanf("%d",&n); printf("%d\n",n-1>>1); return 0; }
B:
想象对树分层即可。

#includetypedef 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 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#include 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 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#include #include 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 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] w[i])); } puts("-1"); return 0; }
AGC 039
A:
除了边上相连的以外都是不变的,分类讨论即可。

#include#include 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 >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#include #include 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的种数,所以还要减一下。

#includetypedef 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 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:
脑筋急转弯?

#includebool 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}$,可以单调队列维护,注意特判。

#includeint 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 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]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 Japanese Student Championship 2019 Qualification A: 送分题,$O(md)$暴力即可。 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 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)$。 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)$。 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)$。 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 复杂度$O(n)$。 C: 对于终止状态,考虑最后得到的那个数,它肯定是加上两边的数得到的,而两边的数都是确定的!所以倒退,看是否能回到原始状态即可。 实现时,找出所有满足$b_i>a_i$且$b_i-a_i\ge b_{i-1}+b_{i+1}$的结点,很明显这些点不会相邻,所以答案与选择顺序无关,同时每次修改只会影响两侧的点,队列/栈模拟,复杂度$O(nloga_i)$。 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
#include
#include
#include
#include
#include
#include
#include
#include