Lesnoe Ozero 2016. BSUIR Open 2016 Finals
A. Street magic
数位DP,设$f[i][j][k]$表示从低到高考虑$x$的后$i$位,$x$和$m$大小关系为$j$,和$n$大小关系为$k$的方案数。
#include<cstdio> #include<algorithm> #include<cstring> using namespace std; const int N=80,P=1000000007; char a[N],b[N]; int n,m,i,j,k,x,lim,f[N][2][2]; /* 0:=m 1:>m 0:<=n 1:>n */ inline void up(int&a,int b){a=a+b<P?a+b:a+b-P;} int main(){ scanf("%s%s",a+1,b+1); n=strlen(a+1); m=strlen(b+1); reverse(a+1,a+n+1); reverse(b+1,b+m+1); lim=max(n,m)+1; a[0]=b[0]=0; for(i=1;i<=n;i++)a[i]-='0'; for(i=n+1;i<=lim;i++)a[i]=0; for(i=1;i<=m;i++)b[i]-='0'; for(i=m+1;i<=lim;i++)b[i]=0; f[0][0][0]=1; for(i=0;i<lim;i++)for(j=0;j<2;j++)for(k=0;k<2;k++)if(f[i][j][k]){ for(x=0;x<10;x++){ int nj,nk; if(x<b[i+1])continue; if(x==b[i+1]){ nj=j; }else nj=1; if(x==a[i+1]){ nk=k; }else{ if(x>a[i+1])nk=1; else nk=0; } up(f[i+1][nj][nk],f[i][j][k]); } } printf("%d",(f[lim][0][0]+f[lim][1][0])%P); }
B. Variety
对于每种颜色单独考虑,从上往下扫描线,用Treap维护每个点往上延伸的高度的笛卡尔树,则每次只需要单点修改或者整体加。
因为数据随机,所以高度随机,直接用高度当优先级复杂度就是正确的。
为了应对多种颜色,应当按时间顺序记录Treap的每次修改以方便还原。
这个做法在$k=1$时会退化,但是当$k=1$时显然$ans=1$,故特判即可。
#include<cstdio> #include<algorithm> #include<cstdlib> #include<vector> using namespace std; const int N=1010; int o; int root,r,c,n,i,j,initroot; int flag; int mx; int cnt; long long ans; struct P{int x,y;P(){}P(int _x,int _y){x=_x,y=_y;}}x,y; struct E{int c,x,y;E(){}E(int _c,int _x,int _y){c=_c,x=_x,y=_y;}}a[1000010]; inline bool cmp(const E&a,const E&b){return a.c==b.c?a.x<b.x:a.c<b.c;} namespace Treap{ const int M=20000010; int h[N],l[N],r[N],size[N],tag[N],sum[N]; int q[M][2]; char w[M]; int top; inline void push(int x,int y,int z){ if(flag)return; top++; if(top>mx)mx=top; w[top]=x; q[top][0]=y; q[top][1]=z; } inline void rec(){ int x=w[top],y=q[top][0],z=q[top][1]; top--; if(x==0)h[y]=z; if(x==1)l[y]=z; if(x==2)r[y]=z; if(x==3)size[y]=z; if(x==4)tag[y]=z; if(x==5)sum[y]=z; } inline void up(int x){ push(3,x,size[x]); push(5,x,sum[x]); size[x]=size[l[x]]+size[r[x]]+1; sum[x]=sum[l[x]]+sum[r[x]]+((h[l[x]]-h[x])*size[l[x]]*(size[l[x]]+1)>>1)+((h[r[x]]-h[x])*size[r[x]]*(size[r[x]]+1)>>1); } inline void add(int x,int p){ if(!x)return; push(0,x,h[x]); push(4,x,tag[x]); h[x]+=p;tag[x]+=p; } inline void pb(int x){ if(tag[x]){ add(l[x],tag[x]),add(r[x],tag[x]); push(4,x,tag[x]); tag[x]=0; } } P split(int x,int y){ if(!x)return P(0,0); pb(x); if(size[l[x]]+1<=y){ P t=split(r[x],y-size[l[x]]-1); push(2,x,r[x]); return r[x]=t.x,up(x),P(x,t.y); } P t=split(l[x],y); push(1,x,l[x]); return l[x]=t.y,up(x),P(t.x,x); } int merge(int x,int y){ if(!x)return y; if(!y)return x; pb(x),pb(y); if(h[x]<h[y]){ int z=merge(r[x],y); push(2,x,r[z]); return r[x]=z,up(x),x; } int z=merge(x,l[y]); push(1,y,l[y]); return l[y]=z,up(y),y; } int build(int a,int b){ if(a>b)return 0; int mid=(a+b)>>1; l[mid]=build(a,mid-1); r[mid]=build(mid+1,b); up(mid); return mid; } } inline void go(int k){ if(k<=0)return; ans-=1LL*Treap::sum[root]*k; long long tmp=1LL*Treap::size[root]*(Treap::size[root]+1)/2; long long st=Treap::h[root]; ans-=1LL*(st*2+k+1)*k/2*tmp; Treap::add(root,k); } inline void init(){ flag=1; Treap::top=0; for(root=i=0;i<=c;i++)Treap::h[i]=Treap::l[i]=Treap::r[i]=Treap::size[i]=Treap::tag[i]=Treap::sum[i]=0; /* root=r;Treap::up(r); for(i=r-1;i;i--){ Treap::r[i]=i+1; Treap::up(i); }*/ //root=1; root=Treap::build(1,r); flag=0; initroot=root; } inline void solve(int L,int R){ int i,j,k; ans+=1LL*r*(r+1)*c*(c+1)>>2; flag=(R-L)>r; int pre=0; for(i=L;i<=R;i=j){ for(j=i;j<=R&&a[i].x==a[j].x;j++); go(a[i].x-pre-1); pre=a[i].x; Treap::add(root,1); for(k=i;k<j;k++){ x=Treap::split(root,a[k].y-1),y=Treap::split(x.y,1); Treap::push(0,y.x,Treap::h[y.x]); Treap::h[y.x]=0; root=Treap::merge(Treap::merge(x.x,y.x),y.y); } ans-=Treap::sum[root]+((1LL*Treap::h[root]*Treap::size[root]*(Treap::size[root]+1))>>1); } go(r-pre); if(flag)init(); else{ root=initroot; while(Treap::top)Treap::rec(); } } int main(){ scanf("%d",&r);c=r; for(i=1;i<=r;i++)for(j=1;j<=c;j++){ scanf("%d",&o); //o=rand()%(r*c)+1; a[++n]=E(o,i,j); } std::sort(a+1,a+n+1,cmp); if(a[1].c==a[n].c)return puts("1"),0; ans=0; init(); for(i=1;i<=n;i=j){ for(j=i;j<=n&&a[i].c==a[j].c;j++); solve(i,j-1); } long long all=1LL*r*(r+1)*c*(c+1)/4; double fin=1.0*ans/all; printf("%.15f",fin); }
C. Crime fiction society
利用线性筛的预处理在$O(\log n)$的时间内分解质因数,同时对于每个质数$p$记录它最小的可能没被占据的倍数,然后暴力枚举即可,时间复杂度$O(n\log n)$。
#include<stdio.h> #include<iostream> #include<string.h> #include<string> #include<ctype.h> #include<math.h> #include<set> #include<map> #include<vector> #include<queue> #include<bitset> #include<algorithm> #include<time.h> using namespace std; void fre() { } #define MS(x, y) memset(x, y, sizeof(x)) #define ls o<<1 #define rs o<<1|1 typedef long long LL; typedef unsigned long long UL; typedef unsigned int UI; template <class T1, class T2>inline void gmax(T1 &a, T2 b) { if (b > a)a = b; } template <class T1, class T2>inline void gmin(T1 &a, T2 b) { if (b < a)a = b; } const int N = 1e7 + 10, M = 0, Z = 1e9 + 7, inf = 0x3f3f3f3f; template <class T1, class T2>inline void gadd(T1 &a, T2 b) { a = (a + b) % Z; } int casenum, casei; int n; int a[N], vis[N], nxt[N]; // int tot,i,j,p[N/8],v[N]; inline int divide(int n){ int ans = 1e9; while(n>1){ int x=v[n]; n/=x; while(vis[nxt[x]]) { nxt[x] += x; /*if(nxt[x] > 1e7) { puts("NO"); while(1); }*/ } ans = min(ans, nxt[x]); } return ans; } void init(){ vis[0] = 1; int top = 1e7; for(i=2;i<=top;i++){ if(!v[i])v[p[tot++]=i]=i; for(j=0;j<tot&&1LL*i*p[j]<=top;j++){ v[i*p[j]]=p[j]; if(i%p[j]==0)break; } } } // void table() { int top = 3e6; a[1] = 1; vis[1] = 1; a[2] = 2; vis[2] = 1; for(int i = 3; i <= top; ++i) { a[i] = divide(a[i - 1]); vis[a[i]] = 1; //printf("%d %d\n", i, a[i]); } //puts("finish"); } int main() { init(); table(); scanf("%d", &n); printf("%d\n", a[n]); return 0; } /* 【trick&&吐槽】 【题意】 【分析】 【时间复杂度&&优化】 */
D. Brand registration
设$f[i][j][k]$表示从某个点开始转了$k$次弯,最后一条线段是$i\rightarrow j$的方案数,则需要转移到与$i$夹角小于$90°$的所有点。
枚举每个$j$,将所有点关于$j$极角排序,那么每次转移都是一个区间,前缀和优化即可。
时间复杂度$O(n^2\log n)$。
#include<cstdio> #include<cmath> #include<algorithm> using namespace std; typedef long long ll; const int N=1010; const double pi2=acos(-1.0)*2.0,pi=acos(-1.0)/2.0,eps=1e-8; int n,m,i,j,k,q[N*2];ll f[N][N],g[N][N],ans; double b[N],c[N*2]; ll s[N*2]; struct P{ int x,y; }a[N]; inline bool cmp(int x,int y){ return b[x]<b[y]; } void dpleft(){ for(i=1;i<=n;i++)for(j=1;j<=n;j++)g[i][j]=0; for(i=1;i<=n;i++){ m=0; for(j=1;j<=n;j++)if(i!=j){ b[j]=atan2(a[j].y-a[i].y,a[j].x-a[i].x); q[++m]=j; } sort(q+1,q+m+1,cmp);//counter clock wise for(j=1;j<=m;j++){ c[j]=b[q[j]]; c[j+m]=c[j]+pi2; q[j+m]=q[j]; } for(j=1;j<=m*2;j++)s[j]=s[j-1]+f[q[j]][i]; for(j=m,k=m*2;j;j--){ k=min(k,j+m-1); while(k>j&&c[k]-c[j]+eps>pi)k--; g[i][q[j]]+=s[k]-s[j]; } } for(i=1;i<=n;i++)for(j=1;j<=n;j++)f[i][j]=g[i][j]; } void dpright(){ for(i=1;i<=n;i++)for(j=1;j<=n;j++)g[i][j]=0; for(i=1;i<=n;i++){ m=0; for(j=1;j<=n;j++)if(i!=j){ b[j]=atan2(a[j].y-a[i].y,a[j].x-a[i].x); q[++m]=j; } sort(q+1,q+m+1,cmp);//counter clock wise for(j=1;j<=m;j++){ c[j]=b[q[j]]; c[j+m]=c[j]+pi2; q[j+m]=q[j]; } for(j=1;j<=m*2;j++)s[j]=s[j-1]+f[q[j]][i]; for(j=m+1,k=1;j<=m*2;j++){ k=max(k,j-m+1); while(k<j&&c[j]-c[k]+eps>pi)k++; g[i][q[j]]+=s[j-1]-s[k-1]; } } for(i=1;i<=n;i++)for(j=1;j<=n;j++)f[i][j]=g[i][j]; } int main(){ scanf("%d",&n); for(i=1;i<=n;i++)scanf("%d%d",&a[i].x,&a[i].y); for(i=1;i<=n;i++)for(j=1;j<=n;j++)if(i!=j)f[i][j]=1; dpleft(); dpright(); dpleft(); dpright(); for(i=1;i<=n;i++)for(j=1;j<=n;j++)if(i!=j)ans+=f[i][j]; printf("%lld",ans/2); }
E. Elections
设$f[n]$表示$n$个人至少需要多少票,枚举因子转移。
#include<stdio.h> #include<iostream> #include<string.h> #include<string> #include<ctype.h> #include<math.h> #include<set> #include<map> #include<vector> #include<queue> #include<bitset> #include<algorithm> #include<time.h> using namespace std; void fre() { } #define MS(x, y) memset(x, y, sizeof(x)) #define ls o<<1 #define rs o<<1|1 typedef long long LL; typedef unsigned long long UL; typedef unsigned int UI; template <class T1, class T2>inline void gmax(T1 &a, T2 b) { if (b > a)a = b; } template <class T1, class T2>inline void gmin(T1 &a, T2 b) { if (b < a)a = b; } const int N = 1e5 + 10, M = 0, Z = 1e9 + 7, inf = 0x3f3f3f3f; template <class T1, class T2>inline void gadd(T1 &a, T2 b) { a = (a + b) % Z; } int casenum, casei; int n; const int INF = 1e9; int f[N]; int a[N]; int m; int main() { while(~ scanf("%d", &n)){ m = 0; for(int i = 1; i * i <= n; i ++){ if(n % i == 0){ a[++ m] = i; if(i * i != n) a[++ m] = n / i; } } sort(a + 1, a + m + 1); for(int i = 1; i <= m; i ++) f[i] = INF; for(int i = 1; i <= m; i ++){ f[i] = a[i] / 2 + 1; for(int j = 1; j < i; j ++){ if(a[i] % a[j] == 0){ // 每份个数 f[i] = min(f[i], f[j] * (a[i] / a[j] / 2 + 1)); } } } printf("%d\n", f[m]); } return 0; } /* 【trick&&吐槽】 【题意】 【分析】 【时间复杂度&&优化】 */
F. Cactus
因为是仙人掌,所以每条边最多属于一个环。
设$f[i][j][k][S]$表示考虑DFS生成树上$i$点的子树,$i$点所在连通块边数为$j$,$i$点到$i$父亲这条边所在环底部的点所在连通块边数为$k$,$i$与环底部的点连通情况为$S$的方案数,分$3$种情况转移即可。
时间复杂度$O(n+m)$。
#include<cstdio> #define rep(i) for(int i=0;i<3;i++) #define FOR(i) for(int i=0;i<2;i++) const int N=100010,M=200010,P=1000000007; int n,m,i,x,y,g[N],v[M<<1],nxt[M<<1],ed; int vis[N],dfn,fa[N]; int isbot[N],istop[N],in[N]; int f[N][3][3][2]; int dp[3][3][2],tmp[3][3][2];//connect with down? int ans; inline void up(int&a,int b){a=a+b<P?a+b:a+b-P;} inline void add(int x,int y){v[++ed]=y;nxt[ed]=g[x];g[x]=ed;} inline void clr(){ rep(i)rep(j)FOR(k)tmp[i][j][k]=0; } inline void go(){ rep(i)rep(j)FOR(k)dp[i][j][k]=tmp[i][j][k]; } void dfs(int x,int y){ fa[x]=y; vis[x]=++dfn; //printf("fa[%d]=%d\n",x,y); for(int i=g[x];i;i=nxt[i]){ int u=v[i]; if(u==y)continue; if(!vis[u]){ dfs(u,x); }else if(vis[u]<vis[x]){ int j=x; isbot[x]=1; while(j!=u){ if(fa[j]==u)istop[j]=1; in[j]=1; j=fa[j]; } } } clr(); go(); dp[0][0][0]=1; for(int i=g[x];i;i=nxt[i]){ int u=v[i]; if(fa[u]!=x)continue; clr(); if(istop[u]){ rep(A)rep(B)FOR(k)if(dp[A][B][k])rep(C)rep(D)FOR(l)if(f[u][C][D][l]){ int w=1LL*dp[A][B][k]*f[u][C][D][l]%P; //not choose (x,u) not choose ex up(tmp[A][B][k],w); //not choose (x,u) choose ex if(A+D+1<3){ //if(A+D+1==2&&x==1)printf("! %d %d %d %d %d\n",A,B,C,D,w); //if(x!=1) //if(x==1)printf("! %d %d %d %d %d\n",A,B,C,D,w); up(tmp[A+D+1][B][k],w); } //choose(x,u) not choose ex if(A+C+1<3)up(tmp[A+C+1][B][k],w); //choose(x,u) choose ex if(A+C+D+2<3){ //if(x==1)printf("! %d %d %d %d %d\n",A,B,C,D,w); up(tmp[A+C+D+2][B][k],w); } } }else if(in[u]){//same circle rep(A)rep(B)FOR(k)if(dp[A][B][k])rep(C)rep(D)FOR(l)if(f[u][C][D][l]){ int w=1LL*dp[A][B][k]*f[u][C][D][l]%P; //not choose(x,u) up(tmp[A][D][0],w); //choose(x,u) if(A+C+1<3){ up(tmp[A+C+1][D][l],w); } } }else{ rep(A)rep(B)FOR(k)if(dp[A][B][k])rep(C)rep(D)FOR(l)if(f[u][C][D][l]){ int w=1LL*dp[A][B][k]*f[u][C][D][l]%P; //not choose(x,u) up(tmp[A][B][k],w); //choose(x,u) if(A+C+1<3)up(tmp[A+C+1][B][k],w); } } go(); } clr(); rep(A)rep(B)FOR(k)if(dp[A][B][k])up(tmp[A][k?A:B][k],dp[A][B][k]); go(); if(isbot[x])rep(i)f[x][i][i][1]=dp[i][0][0]; else rep(i)rep(j)FOR(k)f[x][i][j][k]=dp[i][j][k]; //rep(i)rep(j)if(f[x][i][j])printf("f[%d][%d][%d]=%d\n",x,i,j,f[x][i][j]); } int main(){ scanf("%d%d",&n,&m); for(ed=i=1;i<=m;i++){ scanf("%d%d",&x,&y); add(x,y),add(y,x); } dfs(1,0); rep(i)FOR(j)up(ans,f[1][i][0][j]); printf("%d",ans); } /* 5 5 1 2 3 4 5 4 3 2 3 1 4 4 1 2 1 3 2 3 3 4 */
G. Hard exam
分$n$小和$n$大设计两种处理方法即可。
#include<stdio.h> #include<iostream> #include<string.h> #include<string> #include<ctype.h> #include<math.h> #include<set> #include<map> #include<vector> #include<queue> #include<bitset> #include<algorithm> #include<time.h> using namespace std; void fre() { } #define MS(x, y) memset(x, y, sizeof(x)) #define ls o<<1 #define rs o<<1|1 typedef long long LL; typedef unsigned long long UL; typedef unsigned int UI; template <class T1, class T2>inline void gmax(T1 &a, T2 b) { if (b > a)a = b; } template <class T1, class T2>inline void gmin(T1 &a, T2 b) { if (b < a)a = b; } const int N = 1e7 + 10, M = 0, inf = 0x3f3f3f3f; int casenum, casei; int n, t; set<int> sot; set<int> :: iterator it; int q0, a, b, m, k; const int top = 3e3; int c[N]; int main() { while(~ scanf("%d%d", &n, &t)){ scanf("%d%d%d%d", &q0, &a, &b, &m); LL Z = 1LL * n * n + 1; if(n >= top){ int ans = 0; for(int i = 1; i <= t; i ++){ q0 = (1LL * q0 * a + b) % m; k = q0 % Z; for(int j = 0; j <= n; j ++){ LL x = 1LL * k - 1LL * n * n + 1LL * j * n; LL y = 2LL * j - n; if(y && x % y == 0 && x / y <= n && x / y >= 0 || x == 0 && y == 0) {ans ++; break;} } } printf("%d\n", ans); } else{ int siz = 0; for(int i = 0; i <= n; i ++){ for(int j = i; j <= n; j ++){ c[++ siz] = 1LL * n * n + 2 * i * j - (i + j) * n; } } sort(c + 1, c + siz + 1); int ans = 0; for(int i = 1; i <= t; i ++){ q0 = (1LL * q0 * a + b) % m; k = q0 % Z; if(*lower_bound(c + 1, c + siz + 1, k) == k) ans ++; } printf("%d\n", ans); } } return 0; } /* 【trick&&吐槽】 5 6 9 1 2 999999993 85 155 88 120 53 980090303 【题意】 【分析】 【时间复杂度&&优化】 */
H. A$+$B
$\lfloor\frac{s}{2}\rfloor$与$s-\lfloor\frac{s}{2}\rfloor$是一组合法解。
#include<cstdio> #include<algorithm> using namespace std; typedef long long ll; ll s,m; int main(){ scanf("%lld",&s); m=s/2; printf("%lld %lld",m,s-m); }
I. Credit history
可以看成$2n$个位置,每个位置有一个权值,要求按一些顺序博弈着给这些位置填数,使得每个位置的数字乘以权值之和最大/最小。
设$f[S]$表示还剩$S$集合的位置没有填数,目前是银行在行动时,计算结果的最大可能值。
设$g[S][k]$表示还剩$S$集合的位置没有填数,银行要求还债人填数字$k$,目前是还债人在行动时,计算结果的最小可能值。
DP求出$f$和$g$后每次按照最优转移行动即可。
时间复杂度$O(10n\times 4^n)$。
#include<cstdio> #include<algorithm> using namespace std; typedef long long ll; const ll inf=1LL<<60; int me,n,m,i,j,k; ll p[100],w[100]; ll f[1<<18],g[1<<18][10]; bool vf[1<<18],vg[1<<18][18]; ll calf(int S); ll calg(int S,int o){ if(vg[S][o])return g[S][o]; vg[S][o]=1; ll ret=inf; for(int i=0;i<m;i++)if(S>>i&1)ret=min(ret,calf(S^(1<<i))+o*w[i]); return g[S][o]=ret; } ll calf(int S){ if(!S)return 0; if(vf[S])return f[S]; vf[S]=1; ll ret=-inf; for(int i=0;i<10;i++)ret=max(ret,calg(S,i)); return f[S]=ret; } int getpos(int x,int y){ y=n-y; if(x==1)return y; return y+n; } void write(int S){ printf("%d %d\n",S/n+1,n-S%n); fflush(stdout); } int main(){ scanf("%d%d",&me,&n); for(p[0]=i=1;i<=n;i++)p[i]=p[i-1]*10; for(i=0;i<n;i++)w[m++]=p[i]; for(i=0;i<n;i++)w[m++]=-p[i]; //for(i=0;i<m;i++)printf("%lld\n",w[i]); int S=(1<<m)-1; for(int round=n*2;round--;){ if(me==1){//bank ll ret=-inf; for(i=0;i<10;i++)ret=max(ret,calg(S,i)); for(i=0;i<10;i++)if(calg(S,i)==ret){ printf("%d\n",i); fflush(stdout); break; } int x,y; scanf("%d%d",&x,&y); S^=1<<getpos(x,y); }else{ int x; scanf("%d",&x); ll ret=inf; for(int i=0;i<m;i++)if(S>>i&1)ret=min(ret,calf(S^(1<<i))+x*w[i]); //printf("->%lld\n",ret); for(int i=0;i<m;i++)if(S>>i&1)if(ret==calf(S^(1<<i))+x*w[i]){ write(i); S^=1<<i; break; } } } }
J. Cherry orchard
留坑。