2018-2019 ACM-ICPC, Asia East Continent Finals部分题解
C:显然每p2个数会有一个0循环,其中22 32 52 72的循环会在200个数中出现,找到p2循环的位置就可以知道首位在模p2意义下是多少,并且循环位置几乎是唯一的(对72不满足但可能的位置也很少)。于是这样枚举范围就直接从1e9变成了1e9/44100。然后考虑暴力求μ验证,求μ可以以O(n1/3/lnn)的时间完成,即对n1/3内的质数暴力check并除掉,剩下的直接判断是不是平方数即可。最后只要相信200个数匹配一会就break了就能过了。
#include<bits/stdc++.h> using namespace std; #define ll long long #define N 210 char getc(){char c=getchar();while ((c<'A'||c>'Z')&&(c<'a'||c>'z')&&(c<'0'||c>'9')) c=getchar();return c;} int gcd(int n,int m){return m==0?n:gcd(m,n%m);} int read() { int x=0,f=1;char c=getchar(); while (c<'0'||c>'9') {if (c=='-') f=-1;c=getchar();} while (c>='0'&&c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar(); return x*f; } int prime[N],a[N],p4=-1,p9=-1,p25=-1,p49=-1,cnt=0; inline calc(int x) { for (int i=1;i<=cnt;i++) { if (x%prime[i]==0) x/=prime[i]; if (x%prime[i]==0) return 0; } if (x==1) return 1; int u=sqrt(x); if (u*u==x||(u+1)*(u+1)==x) return 0; else return 1; } signed main() { for (int i=2;i<=1000;i++) { bool flag=0; for (int j=2;j<i;j++) if (i%j==0) {flag=1;break;} if (!flag) prime[++cnt]=i; } for (int i=0;i<200;i++) a[i]=getc()-'0'; for (int i=0;i<4;i++) { bool flag=0; for (int j=i;j<200;j+=4) if (a[j]!=0) {flag=1;break;} if (!flag) {p4=(4-i)%4;break;} } if (p4==-1) {cout<<-1;return 0;} for (int i=0;i<9;i++) { bool flag=0; for (int j=i;j<200;j+=9) if (a[j]!=0) {flag=1;break;} if (!flag) {p9=(9-i)%9;break;} } if (p9==-1) {cout<<-1;return 0;} for (int i=0;i<25;i++) { bool flag=0; for (int j=i;j<200;j+=25) if (a[j]!=0) {flag=1;break;} if (!flag) {p25=(25-i)%25;break;} } if (p25==-1) {cout<<-1;return 0;} int P=4*9*25*49,r; for (int i=0;i<49;i++) { bool flag=0; for (int j=i;j<200;j+=49) if (a[j]!=0) {flag=1;break;} if (!flag) { p49=(49-i)%49; for (int k=0;k<P;k++) if (k%4==p4&&k%9==p9&&k%25==p25&&k%49==p49) {r=k;break;} for (int k=r;k+200<=1000000001;k+=P) { bool flag=0; for (int x=k;x<k+200;x++) if (calc(x)!=a[x-k]) {flag=1;break;} if (!flag) {cout<<k;return 0;} } } } cout<<-1; return 0; //NOTICE LONG LONG!!!!! }
I:考虑将增加A的贡献直接加入dp值。要算贡献需要知道之后攻击了多少次,攻击时间的和,于是设f[i][j][k]为前i次后期望之后攻击j次攻击时间和为k的最大伤害,转移显然。倒着dp和其本质相同。
#include<bits/stdc++.h> using namespace std; #define ll long long #define N 110 char getc(){char c=getchar();while ((c<'A'||c>'Z')&&(c<'a'||c>'z')&&(c<'0'||c>'9')) c=getchar();return c;} int gcd(int n,int m){return m==0?n:gcd(m,n%m);} int read() { int x=0,f=1;char c=getchar(); while (c<'0'||c>'9') {if (c=='-') f=-1;c=getchar();} while (c>='0'&&c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar(); return x*f; } int n,a[N],b[N],c[N],s[N]; ll f[2][N][N*N]; signed main() { int T=read(); while (T--) { n=read(); memset(f,0,sizeof(f)); for (int i=1;i<=n;i++) a[i]=read(),b[i]=read(),c[i]=read(); s[n]=n; for (int i=n-1;i>=1;i--) s[i]=s[i+1]+i; for (int i=0;i<n;i++) { memset(f[i&1^1],0,sizeof(f[i&1^1])); for (int j=0;j<=n-i;j++) for (int k=0;k<=s[i+1];k++) { f[i&1^1][j][k]=max(f[i&1^1][j][k],f[i&1][j][k]+1ll*(k-(i+1)*j)*b[i+1]), f[i&1^1][j][k]=max(f[i&1^1][j][k],f[i&1][j][k]+1ll*j*c[i+1]); if (j&&(k>=i+1)) { f[i&1^1][j-1][k-(i+1)]=max(f[i&1^1][j-1][k-(i+1)],f[i&1][j][k]+a[i+1]); } } } cout<<f[n&1][0][0]<<endl; } return 0; //NOTICE LONG LONG!!!!! }
J:看做一个零和博弈,那个式子看成先手的收益,相反数看成后手的收益。考虑纳什均衡,则要尽量使后手选择各后缀的收益均相同(事实上若有某后缀包含另一后缀,相同是不可能实现的,但容易发现被包含的后缀不分配概率一定最优,实际上这个分析没有任何卵用)。考虑对后缀数组以height最小值分治,这样所有跨过该位置的两后缀lcp即为该height值。递归下去算出两侧的最优答案,容易发现合并时两侧保留原本的概率分配是最优的,因为两侧自身如何分配互相之间并不相干,那么只要给两侧分配概率使后手选择两侧的收益相同即可。
#include<bits/stdc++.h> using namespace std; #define ll long long #define N 200010 char getc(){char c=getchar();while ((c<'A'||c>'Z')&&(c<'a'||c>'z')&&(c<'0'||c>'9')) c=getchar();return c;} int gcd(int n,int m){return m==0?n:gcd(m,n%m);} int read() { int x=0,f=1;char c=getchar(); while (c<'0'||c>'9') {if (c=='-') f=-1;c=getchar();} while (c>='0'&&c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar(); return x*f; } int T,n,rk[N<<1],tmp[N<<1],sa[N],sa2[N],cnt[N],h[N],v[N],f[N][20],LG2[N]; char s[N]; void make() { int m=26; for (int i=1;i<=n*2;i++) rk[i]=tmp[i]=0; for (int i=1;i<=m;i++) cnt[i]=0; for (int i=1;i<=n;i++) cnt[rk[i]=(s[i]-'a'+1)]++; for (int i=1;i<=m;i++) cnt[i]+=cnt[i-1]; for (int i=n;i>=1;i--) sa[cnt[rk[i]]--]=i; for (int k=1;k<=n;k<<=1) { int p=0; for (int i=n-k+1;i<=n;i++) sa2[++p]=i; for (int i=1;i<=n;i++) if (sa[i]>k) sa2[++p]=sa[i]-k; for (int i=1;i<=m;i++) cnt[i]=0; for (int i=1;i<=n;i++) cnt[rk[i]]++; for (int i=1;i<=m;i++) cnt[i]+=cnt[i-1]; for (int i=n;i>=1;i--) sa[cnt[rk[sa2[i]]]--]=sa2[i]; for (int i=1;i<=n*2;i++) tmp[i]=rk[i]; p=1,rk[sa[1]]=1; for (int i=2;i<=n;i++) { if (tmp[sa[i]]!=tmp[sa[i-1]]||tmp[sa[i]+k]!=tmp[sa[i-1]+k]) p++; rk[sa[i]]=p; } m=p;if (m==n) break; } for (int i=1;i<=n;i++) { h[i]=max(h[i-1]-1,0); while (s[i+h[i]]==s[sa[rk[i]-1]+h[i]]) h[i]++; } for (int i=1;i<=n;i++) v[i]=h[sa[i]]; for (int i=1;i<=n;i++) f[i][0]=i; for (int i=2;i<=n;i++) { LG2[i]=LG2[i-1]; if ((2<<LG2[i])<=i) LG2[i]++; } for (int j=1;j<20;j++) for (int i=1;i<=n;i++) if (v[f[i][j-1]]<v[f[min(n,i+(1<<j-1))][j-1]]) f[i][j]=f[i][j-1]; else f[i][j]=f[min(n,i+(1<<j-1))][j-1]; } int query(int x,int y) { x++; return v[f[x][LG2[y-x+1]]]<v[f[y-(1<<LG2[y-x+1])+1][LG2[y-x+1]]]?f[x][LG2[y-x+1]]:f[y-(1<<LG2[y-x+1])+1][LG2[y-x+1]]; } long double solve(int l,int r) { if (l==r) return n-sa[l]+1; int mid=query(l,r),h=v[mid]; long double x=solve(l,mid-1),y=solve(mid,r); long double k=(y-h)/(x+y-h-h); return x*k+h*(1-k); } signed main() { #ifndef ONLINE_JUDGE freopen("j.in","r",stdin); freopen("j.out","w",stdout); #endif T=read(); while (T--) { scanf("%s",s+1);n=strlen(s+1); make(); printf("%.12f\n",(double)solve(1,n)); } return 0; //NOTICE LONG LONG!!!!! }
K:考虑一个暴力dp,即设f[i][j]为以i为左端点要合成j时右端点至少为多少。转移显然有f[i][j]=min(f[f[i][j-1]+1][j-1],nxt[i][j]),其中nxt[i][j]为i位置之后出现的第一个j的位置。
显然f[i][j]随i增加单调不降。这样对于询问(l,r,k),只要二分出满足f[i][k]<=r的最大i,然后答案即为(i-l+1)*(r+1)-f[l..i][k],也即要知道f数组某一段的和。
考虑怎么不暴力地求f。观察转移,可以发现是一个类似于倍增的过程,虽然还有个取min的步骤,但仍然可以大胆猜想,对于所有j,f[i][j]构成的相同数连续段数量之和是O(nlogn)级别的。
这样维护f数组的连续段即可。具体地,离线处理,将询问按要合成的大小从小到大排序。更新dp数组时从前往后处理,记录f的前缀和,更新完合并相邻的相同段。
#include<bits/stdc++.h> using namespace std; #define ll long long #define N 200010 int read() { int x=0,f=1;char c=getchar(); while (c<'0'||c>'9') {if (c=='-') f=-1;c=getchar();} while (c>='0'&&c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar(); return x*f; } int n,m,q,a[N],nxt[N],p[N],top; ll ans[N]; struct data { int l,r,x,i;ll s; bool operator <(const data&a) const { return x<a.x; } }b[N],stk[N],tmp[N]; int getdp(int x) { if (x>n) return n+1; int l=1,r=top,ans; while (l<=r) { int mid=l+r>>1; if (stk[mid].l<=x) ans=mid,l=mid+1; else r=mid-1; } return stk[ans].x; } ll getdps(int x) { if (x==0) return 0; int l=1,r=top,ans=0; while (l<=r) { int mid=l+r>>1; if (stk[mid].r<=x) ans=mid,l=mid+1; else r=mid-1; } return stk[ans].s+1ll*(x-stk[ans].r)*stk[ans+1].x; } int main() { #ifndef ONLINE_JUDGE freopen("k.in","r",stdin); freopen("k.out","w",stdout); #endif n=read(),m=read(),q=read(); for (int i=1;i<=n;i++) a[i]=read(); for (int i=1;i<=q;i++) b[i].l=read(),b[i].r=read(),b[i].x=read(),b[i].i=i; sort(b+1,b+q+1); for (int i=1;i<=m;i++) p[i]=n+1; for (int i=n;i>=1;i--) nxt[i]=p[a[i]],p[a[i]]=i; top=1;stk[1].l=1,stk[1].r=n,stk[1].x=n+1,stk[1].s=1ll*(n+1)*n; int cur=0; for (int i=1;i<=m;i++) { int x=p[i],top_tmp=0; for (int j=1;j<=top;j++) { int y=getdp(stk[j].x+1),last=stk[j].l-1; while (stk[j].r>=x) { top_tmp++; tmp[top_tmp].l=last+1,tmp[top_tmp].r=x,tmp[top_tmp].x=x; tmp[top_tmp].s=tmp[top_tmp-1].s+1ll*(x-last)*x; last=x;x=nxt[x]; } if (last<stk[j].r) { top_tmp++; tmp[top_tmp].l=last+1,tmp[top_tmp].r=stk[j].r,tmp[top_tmp].x=min(x,y); tmp[top_tmp].s=tmp[top_tmp-1].s+1ll*(stk[j].r-last)*min(x,y); } } top=0; for (int j=1;j<=top_tmp;j++) { int t=j; while (t<top_tmp&&tmp[t+1].x==tmp[j].x) t++; top++;stk[top].l=tmp[j].l,stk[top].r=tmp[t].r,stk[top].x=tmp[j].x,stk[top].s=tmp[t].s; j=t; } for (int j=1;j<=top;j++) stk[j].s=stk[j-1].s+1ll*(stk[j].r-stk[j].l+1)*stk[j].x; while (b[cur+1].x==i) { cur++; int l=1,r=top,k=0; while (l<=r) { int mid=l+r>>1; if (stk[mid].x<=b[cur].r) k=stk[mid].r,l=mid+1; else r=mid-1; } if (k>=b[cur].l) ans[b[cur].i]=1ll*(k-b[cur].l+1)*(b[cur].r+1)-(getdps(k)-getdps(b[cur].l-1)); } //for (int j=1;j<=top;j++) cout<<stk[j].l<<' '<<stk[j].r<<' '<<stk[j].x<<' '<<stk[j].s<<endl;cout<<endl; } for (int i=1;i<=q;i++) printf("%lld\n",ans[i]); return 0; }