sunshine的神题(from2015.10.23)
codevs1513 皇帝的烦恼
题目大意:给定n个点成环,每个点要求染ai个颜色,要求相邻两点不能有相同颜色,求最少颜色。
思路:二分+dp。fi[i]表示i这个点和1最少相同颜色;gi[i]表示i这个点和1最多颜色,互相更新一下。如果fi[n]==0则可以(中间还要注意如果相邻两个点的和大于x也要返回false)。
orz sunshineorz sunshineorz sunshine
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define maxm 20005 using namespace std; int ai[maxm]={0},fi[maxm]={0},gi[maxm]={0},n; bool judge(int x){ int i,j; memset(fi,127,sizeof(fi)); memset(gi,0,sizeof(gi)); fi[1]=gi[1]=ai[1]; for (i=2;i<=n;++i){ if (ai[i]+ai[i-1]>x) return false; fi[i]=max(0,ai[i]-((x-ai[1])-(ai[i-1]-gi[i-1]))); gi[i]=min(ai[i],ai[1]-fi[i-1]); }return fi[n]==0; } int main(){ int i,l,r=0,mid;scanf("%d",&n); for (i=1;i<=n;++i){scanf("%d",&ai[i]);r+=ai[i];} l=ai[1]; while(l!=r){ if (judge(mid=(l+r)>>1)) r=mid; else l=mid+1; }printf("%d\n",l); }
bzoj1196 公路修建问题
题目大意:给定n个点,m条边,每条边有两种建设价值,求一级公路不少于k条的最大权值最小的值。
思路:二分最大权值,对于一级造价小于的进行生成树,如果边数少于k就返回false;然后对于二级造价做,如果最后边数少于n-1也要返回false。
orz sunshineorz sunshineorz sunshine
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define maxm 20005 using namespace std; struct use{ int st,en,v1,v2; }edge[maxm]={0}; int fa[maxm]={0},n,m,k; int root(int x){ if (fa[x]!=x) fa[x]=root(fa[x]); return fa[x]; } bool judge(int x){ int i,j,r1,r2;bool f=true; for (i=1;i<=n;++i) fa[i]=i; for (j=0,i=1;i<=m;++i){ if (edge[i].v1>x) continue; r1=root(edge[i].st);r2=root(edge[i].en); if (r1!=r2){fa[r1]=r2;++j;} }if (j<k) return false; for (i=1;i<=m;++i){ if (edge[i].v2>x) continue; r1=root(edge[i].st);r2=root(edge[i].en); if (r1!=r2){fa[r1]=r2;++j;} }return j==n-1; } int main(){ int i,j,l,r,mid; scanf("%d%d%d",&n,&k,&m); for (i=1;i<=m;++i) scanf("%d%d%d%d",&edge[i].st,&edge[i].en,&edge[i].v1,&edge[i].v2); l=0;r=30000; while(l!=r){ mid=(l+r)>>1; if (judge(mid)) r=mid; else l=mid+1; }printf("%d\n",l); }
noip2013 华容道
题目大意:给定一张棋盘,给定一个空格子,目标格子,目标位置,求最少次数让目标格子到目标位置。
思路:先bfs处理出fi[i][j][a][b](i,j这个点,空格子在a方向,要将空格子移到b方向的次数),对于每个询问,先bfs将空格移到目标格子附近,在spfa最短路,求出最后答案。
注意:开两个队列,因为在spfa加点的时候还会用到bfs!!!
orz sunshineorz sunshineorz sunshine
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define maxm 35 #define len 1000000 using namespace std; struct use{ int x,y,dep; }que[len+1],q[len+1]; int map[maxm][maxm]={0},fi[maxm][maxm][4][4],dx[4]={0,1,-1,0},dy[4]={1,0,0,-1},n,m,ex,ey, sx,sy,tx,ty,visit[maxm][maxm]={0},vcnt=0,dis[maxm][maxm][4],inf; bool vi[maxm][maxm][4]; int bfs(int i,int j,int x,int y,int xx,int yy){ if (x==xx&&y==yy) return 0; int head=0,tail=0,k,xi,yi;use u; que[++tail]=(use){x,y,0}; visit[i][j]=visit[x][y]=++vcnt; while(head!=tail){ u=que[head=head%len+1]; for (k=0;k<4;++k){ xi=u.x+dx[k];yi=u.y+dy[k]; if (xi==xx&&yi==yy) return u.dep+1; if (!map[xi][yi]||visit[xi][yi]==vcnt) continue; que[tail=tail%len+1]=(use){xi,yi,u.dep+1}; visit[xi][yi]=vcnt; } }return inf; } void pre(){ int i,j,a,b,x,y,xx,yy; memset(fi,127/3,sizeof(fi));inf=fi[0][0][0][0]; for (i=1;i<=n;++i) for (j=1;j<=m;++j){ if (!map[i][j]) continue; for (a=0;a<4;++a){ if (!map[x=i+dx[a]][y=j+dy[a]]) continue; for (b=0;b<4;++b){ if (!map[xx=i+dx[b]][yy=j+dy[b]]) continue; fi[i][j][a][b]=bfs(i,j,x,y,xx,yy); } } } } int work(){ if (!map[ex][ey]||!map[sx][sy]||!map[tx][ty]) return -1; if (sx==tx&&sy==ty) return 0; int i,j,k,x,y,xi,yi,ans,head=0,tail=0;use u; memset(dis,127/3,sizeof(dis));ans=dis[0][0][0]; for (k=0;k<4;++k){ x=sx+dx[k];y=sy+dy[k]; if (!map[x][y]) continue; dis[sx][sy][k]=bfs(sx,sy,ex,ey,x,y); q[++tail]=(use){sx,sy,k}; vi[sx][sy][k]=true; }while(head!=tail){ u=q[head=head%len+1]; vi[u.x][u.y][u.dep]=false; for (k=0;k<4;++k){ x=u.x+dx[k];y=u.y+dy[k]; if (!map[x][y]) continue; if ((j=dis[u.x][u.y][u.dep]+fi[u.x][u.y][u.dep][k]+1)<dis[x][y][3-k]){ dis[x][y][3-k]=j; if (!vi[x][y][3-k]){ vi[x][y][3-k]=true; q[tail=tail%len+1]=(use){x,y,3-k}; } } } }for (k=0;k<4;++k) ans=min(ans,dis[tx][ty][k]); return (ans==inf ? -1 : ans); } int main(){ int i,j,q;scanf("%d%d%d",&n,&m,&q); for (i=1;i<=n;++i) for (j=1;j<=m;++j) scanf("%d",&map[i][j]); pre(); for (i=1;i<=q;++i){ scanf("%d%d%d%d%d%d",&ex,&ey,&sx,&sy,&tx,&ty); printf("%d\n",work()); } }
bzoj4245 OR-XOR
题目大意:给定n个数,将他们分成m份,每份的权值是异或和,总权值是每份权值或和,求最小权值。
思路:考虑每一位,如果前缀异或和为0的位数>=m且n处的为0,那么就可以分出m份,这时候他们的分法对后面有影响,我们要把所有这位上为1的标记为不可选;如果不满足前面的,说明这一位肯定要给答案贡献1<<x,并且不会影响后面的分法。
orz sunshineorz sunshineorz sunshine
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define maxm 500005 #define LL long long using namespace std; LL ai[maxm],sum[maxm]={0LL}; bool vi[maxm]={false}; int main(){ int n,m,i,j,cnt;LL ans=0; scanf("%d%d",&n,&m); for (i=1;i<=n;++i){scanf("%I64d",&ai[i]);sum[i]=sum[i-1]^ai[i];} for (i=60;i>=0;--i){ for (cnt=0,j=1;j<=n;++j) if (((sum[j]>>i)&1)==0&&!vi[j]) ++cnt; if (cnt>=m&&((sum[n]>>i)&1)==0){ for (j=1;j<=n;++j) if ((sum[j]>>i)&1) vi[j]=true; }else ans|=1LL<<i; }printf("%I64d\n",ans); }
bzoj1912 巡逻
题目大意:给定一棵树,可以填1~2条边,然后要从1遍历所有边回到1,求走过边的最少条数。
思路:如果我们知道每次填边形成的那个环的大小,是可以直接算出答案的。所以我们要让这两个环尽量大,同时这两个环的相交部分对答案的贡献为0(第一遍少走一次,第二遍又走一次,所以我们要把第一次的最大环的边权标记为-1)。找最大环(链)的过程中可以记录每个点延伸的最大次大值及其连边就可以了。
orz sunshineorz sunshineorz sunshine
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define maxm 200005 #define inf 2100000000 using namespace std; int point[maxm]={0},en[maxm]={0},next[maxm]={0},tot=0,va[maxm]={0},ansi=0,ne[maxm][2]={0}, fi[maxm]={0}; void add(int u,int v){ next[++tot]=point[u];point[u]=tot;en[tot]=v;va[tot]=1; next[++tot]=point[v];point[v]=tot;en[tot]=u;va[tot]=1; } void dfs(int u,int fa,int &ans){ int i,j,v,maxn=0,cmaxn=0; for (i=point[u];i;i=next[i]){ if ((v=en[i])==fa) continue;dfs(v,u,ans); if (fi[v]+va[i]>maxn){ cmaxn=maxn;ne[u][1]=ne[u][0]; maxn=fi[v]+va[i];ne[u][0]=i; }else if (fi[v]+va[i]>cmaxn){cmaxn=fi[v]+va[i];ne[u][1]=i;} }if (cmaxn+maxn>ans){ans=cmaxn+maxn;ansi=u;} fi[u]=maxn; } void change(int u){ if (ne[u][0]) {va[ne[u][0]]=-1;change(en[ne[u][0]]);} if (u==ansi&&ne[u][1]) {va[ne[u][1]]=-1;change(en[ne[u][1]]);} } int main(){ int n,k,i,j,u,v,ans1=0,ans2=0;scanf("%d%d",&n,&k); for (i=1;i<n;++i){scanf("%d%d",&u,&v);add(u,v);} dfs(1,0,ans1);change(ansi); if (k>1) dfs(1,0,ans2); printf("%d\n",2*(k+n-1)-ans1-ans2-k); }
bzoj1228 幸运数字
题目大意:求a~b中是幸运数字倍数的数的个数(幸运数字是指十进制中只有6、8的数字)。
思路:首先预处理出所有幸运数字,然后dfs容斥。优化:1)可以发现如果两个幸运数字x、y,x%y=0就可以把x删掉;2)搜索的时候倒着搜(这个优化快了很多)。
orz sunshineorz sunshineorz sunshine
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define LL long long #define LD double #define N 3000 using namespace std; LL ai[N]={0},ci[N]={0},a,b,ans=0LL;int sum=0; bool vi[N]={false}; LL gcd(LL x,LL y){return (!y ? x : gcd(y,x%y));} void pre(LL x){ if (x>b) return;LL y; if ((y=x*10LL+6LL)<=b) pre(ai[++sum]=y); if ((y=x*10LL+8LL)<=b) pre(ai[++sum]=y); } LL lcm(LL x,LL y,LL gc){return x/gc*y;} LD ldlcm(LL x,LL y,LL gc){return (LD)(x/gc)*(LD)y;} void dfs(int la,int cn,LL y){ if (cn){ if (cn%2) ans=ans+b/y-a/y; else ans=ans-b/y+a/y; }LL gc; for (int i=la-1;i;--i){ gc=gcd(ai[i],y); if (ldlcm(ai[i],y,gc)>(LD)b) continue; dfs(i,cn+1,lcm(ai[i],y,gc)); } } int main(){ LL i,j,t=0;scanf("%I64d%I64d",&a,&b); --a;pre(0LL);sort(ai+1,ai+sum+1); for (i=1;i<=sum;++i) if (!vi[i]){ ci[++t]=ai[i]; for (j=i+1;j<=sum;++j) if (ai[j]%ai[i]==0) vi[j]=true; }for (sum=t,i=1;i<=t;++i) ai[i]=ci[i]; dfs(sum+1,0,1LL); printf("%I64d\n",ans); }
bzoj2241 打地鼠
题目大意:nm的格子内有不同数目的鼹鼠,一个长宽自定,但定后不能更改的锤子打地鼠,要求锤子砸下去的地方都有一只,并且只会打死一只,求最少砸几次。
思路:暴力。先枚举长宽,判断和%面积是否=0,因为要求每次砸的地方都有鼹鼠;然后枚举起点贪心算一下能否满足就可以了。
orz sunshineorz sunshineorz sunshine
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define N 105 #define inf 2100000000 using namespace std; int n,m,ai[N][N],bi[N][N],ans,sum=0; int work(int a,int b){ int i,j,x,y,lu,ru,mn,mx; lu=n-a+1;ru=m-b+1; for (i=1;i<=n;++i) for (j=1;j<=m;++j) bi[i][j]=ai[i][j]; for (i=1;i<=lu;++i) for (j=1;j<=ru;++j){ if (!bi[i][j]) continue; mn=inf;mx=0; for (x=i+a-1;x>=i;--x){ for (y=j+b-1;y>=j;--y){ mn=min(bi[x][y],mn); mx=max(bi[x][y],mx); }if (!mn) break; }if (!mn) continue; for (x=i+a-1;x>=i;--x) for (y=j+b-1;y>=j;--y) bi[x][y]-=mn; } for (i=1;i<=n;++i) for (j=1;j<=m;++j) if (bi[i][j]) return inf; return sum/(a*b);} int main(){ int i,j,a,b; scanf("%d%d",&n,&m);ans=0; for (i=1;i<=n;++i) for (j=1;j<=m;++j){ scanf("%d",&ai[i][j]);ans+=ai[i][j];sum+=ai[i][j]; }for (i=n;i;--i) for (j=m;j;--j) if (sum%(i*j)==0&&sum/(i*j)<=ans) ans=min(ans,work(i,j)); printf("%d\n",ans); }
bzoj4002 有意义的字符串
题目大意:求((b+gen d)/2)^n%p的值。( 0<b^2< = d<(b+1)2< = 10^18,n< = 10^18,并且 b mod 2=1,d mod 4=1)
思路:特征方程中有fn=c1fn-1+c2fn-2,r^2=c1*r+c2;考虑fn=(((b+gen d)/2)^n+((b-gen d)/2)^n)%p,可以写出递推公式fn=bfn-1+(d-b^2)/4fn-2,然后可以矩乘算出fn(f0=2,f1=b),减去((b-gen d)/2)%p,因为下取整和数据范围的限制,所以只有在b^2!=d&&n是奇数的时候为1。
注意:n=0的时候答案是1.
orz sunshineorz sunshineorz sunshine
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define UL unsigned long long #define N 3 #define p 7528443412579576937ULL using namespace std; UL mul(UL x,UL y){ if (!y) return 0LL; if (y==1) return x%p; UL mm=mul(x,y/2LL); if (y%2) return ((mm+mm)%p+x)%p; else return (mm+mm)%p;} struct mat{ UL x[N][N]; mat operator*(const mat&xx)const{ int i,j,k;mat y; for (i=1;i<=2;++i) for (j=1;j<=2;++j){ y.x[i][j]=0LL; for (k=1;k<=2;++k) y.x[i][j]=(y.x[i][j]+mul(x[i][k],xx.x[k][j]))%p; }return y;} }ai,bi; UL b,d; void mi(UL x){ bi.x[1][1]=2LL;bi.x[1][2]=b; for (;x;x>>=1){ if (x&1) bi=bi*ai; ai=ai*ai;}} int main(){ UL i,j,n;cin>>b>>d>>n; ai.x[1][1]=0LL;ai.x[1][2]=(d-b*b)/4LL; ai.x[2][1]=1LL;ai.x[2][2]=b; mi(n);cout<<(bi.x[1][1]-(b*b!=d && !(n%2))+p)%p<<endl; }
bzoj3813 奇数国
题目大意:1~100000的数组中的数唯一分解后的质数都是前60个,有修改操作,求a~b中元素乘积的phi。
思路:建60个树状数组,然后暴力统计,计算phi就可以过了。
orz sunshineorz sunshineorz sunshine
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define N 100005 #define M 100000000000LL #define up 60 #define p 19961993LL #define LL long long using namespace std; int flag[N],prime[N],tr[up+1][N]={0},ai[up+1][N]={0}; inline int in(){ char ch=getchar();int x=0; while(ch<'0'||ch>'9') ch=getchar(); while(ch>='0'&&ch<='9'){ x=x*10+ch-'0';ch=getchar(); }return x;} inline LL mi(LL x,int y){ if (y==0) return 1LL; if (y==1) return x; LL mm=mi(x,y/2); if (y%2) return mm*mm%p*x%p; return mm*mm%p;} inline void shai(){ int i,j; for (i=2;i<N;++i){ if (!flag[i]){ prime[++prime[0]]=i; if (prime[0]==up) break; }for (j=1;j<=prime[0]&&i*prime[j]<N;++j){ flag[i*prime[j]]=true; if (i%prime[j]==0) break; } }} inline int lowbit(int x){return x&(-x);} inline void ins(int k,int l,int x){for (;l<N;l+=lowbit(l)) tr[k][l]+=x;} inline int ask(int k,int x){ int ans=0; for (;x;x-=lowbit(x)) ans+=tr[k][x]; return ans;} inline void pre(){ int i,j,cnt;shai(); for (i=1;i<N;++i) ins(2,i,ai[2][i]=1); } inline void insert(int b,int c){ int i,cnt; for (i=1;i<=up;++i){ for (cnt=0;c && c%prime[i]==0;c/=prime[i],++cnt); ins(i,b,-ai[i][b]); ins(i,b,ai[i][b]=cnt); } } int main(){ int x,a,b,c,i,j;LL ans; x=in();pre(); while(x--){ a=in();b=in();c=in(); if (a) insert(b,c); else{ ans=1LL; for (i=1;i<=up;++i){ j=ask(i,c)-ask(i,b-1); if (j) ans=ans*mi(prime[i],j-1)%p*(LL)(prime[i]-1)%p; }printf("%lld\n",ans); } } }
正解是:phi还有一个公式是phi(n)=n*(1-1/p1)*(1-1/p2)...,所以可以压位表示一段区间内60个素数的选择情况,再维护区间积,算出答案就可以了。(!!!)
bzoj1027 合金
题目大意:给定m种三种金属的合金和金属的比例(之和为1),可以选任意种合金以任意比例混合得到新合金,给定n种要求的合金比例,问最少需要多少种给定的合金。
思路:知道其中两种合金就可以得到第三种合金的比例,所以可以只考虑二维的情况。放在平面上,一个合金可以被某些合成当且仅当这个合金在那些合金的平面凸包,所以要求最小点数(点数=边数)的能包含所有点的凸包,对于n个要求合金都在某条边一侧的情况,可以连有向边1,其他的为inf,求出最小环就是答案(因为这里没有自环,所以floyed的min fi[i][i]就是答案)。
注意:有一些特殊情况:1)共点的情况;2)共线的情况;3)点要在线段上或者直线同侧才能连边1。
orz sunshineorz sunshineorz sunshine
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<cmath> #define N 505 #define LD double #define eps 1e-9 using namespace std; int cmp(LD x,LD y){ if (x-y>eps) return 1; if (y-x>eps) return -1; return 0;} struct point{ LD x,y; bool operator<(const point&a)const{ return (cmp(x,a.x)==0 ? cmp(y,a.y)<0 : cmp(x,a.x)<0);} }ai[N],bi[N]; LD cross(point a,point b){return a.x*b.y-b.x*a.y;} LD dot(point a,point b){return a.x*b.x+a.y*b.y;} point operator-(point a,point b){return (point){a.x-b.x,a.y-b.y};} bool operator==(point a,point b){return (cmp(a.x,b.x)==0&&cmp(a.y,b.y)==0);} LD sqr(LD x){return x*x;} LD di(point a){return sqrt(sqr(a.x)+sqr(a.y));} int dis[N][N],n,m; int judge(){ int i; for (i=2;i<m;++i) if (cmp(cross(ai[i]-ai[i-1],ai[i+1]-ai[i]),0.)!=0) break; if (i<m) return 0; for (i=2;i<n;++i) if (cmp(cross(bi[i]-bi[i-1],bi[i+1]-bi[i]),0.)!=0) break; if (i<n) return 0; sort(ai+1,ai+m+1); if (cmp(cross(ai[m]-ai[1],bi[n]-bi[1]),0.)!=0) return -1; for (i=1;i<=n;++i) if (cmp(dot(bi[i]-ai[1],bi[i]-ai[m]),0.)>0) return -1; return 2;} int main(){ int i,j,k,mc,ci;LD cc; bool ff=false,f1=false; scanf("%d%d",&m,&n); for (i=1;i<=m;++i) scanf("%lf%lf%lf",&ai[i].x,&ai[i].y,&cc); for (i=1;i<=n;++i) scanf("%lf%lf%lf",&bi[i].x,&bi[i].y,&cc); memset(dis,127/3,sizeof(dis));mc=dis[0][0]; for (i=1;i<=n;++i) for (j=1;j<=n;++j) if (!(bi[i]==bi[j])) ff=true; for (i=1;i<=m;++i) for (j=i+1;j<=m;++j){ if (ai[i]==ai[j]) continue;f1=true; for (ci=k=1;k<=n;++k) if (cmp(cross(bi[k]-ai[i],ai[j]-ai[i]),0.)!=0){ci=k;break;} cc=cross(bi[ci]-ai[i],ai[j]-ai[i]); for (k=1;k<=n;++k) if ((ci=cmp(cross(bi[k]-ai[i],ai[j]-ai[i])*cc,0.))<0 ||(ci==0&&cmp(dot(bi[k]-ai[i],bi[k]-ai[j]),0.)>0)) break; if (k<=n) continue; if (cmp(cc,0.)==0) dis[i][j]=dis[j][i]=1; else{ if (cmp(cc,0.)<0) dis[i][j]=1; else dis[j][i]=1; } } if (!f1){ if (!ff&&ai[1]==bi[1]) printf("1\n"); else printf("-1\n"); }else{ if (!ff){ for (i=1;i<=m;++i) if (ai[i]==bi[1]) break; if (i<=m){printf("1\n");return 0;} }if ((i=judge())!=0) printf("%d\n",i); else{ for (k=1;k<=m;++k) for (i=1;i<=m;++i) for (j=1;j<=m;++j) dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]); for (i=1;i<=m;++i) mc=min(mc,dis[i][i]); printf("%d\n",(mc==dis[0][0] ? -1:mc)); } } }
bzoj1068 压缩
题目大意:给定一个串,可以在某个位置加入M/R,R表示从上一个M开始重复,开头默认有一个M,求最短的压缩后的长度。
思路:区间dp。fi[i][j][k]表示i~j中有没有M(0表示一定没有,1表示可以有)(!!!)。转移的时候有三种情况,如果可以有M,min fi[i][cc][k]+1+fi[cc+1][j][k];如果可以对折,fi[i][mid][0]+1;还可以一部分改动,一部分不动,就是fi[i][cc][k]+r-cc。
(一开始自己想的是fi[i][j][k]表示i~j是否是两边有MR的对折区间,然后更新,但是处理不了dabcabcdabcabc这种M在区间中间导致R的作用范围改变的情况。)
orz sunshineorz sunshineorz sunshine
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define N 55 using namespace std; char ss[N]; int fi[N][N][2]={0}; bool judge(int l,int r){ if ((r-l+1)%2) return false; int i,j; j=(r-l+1)/2; for (i=1;i<=j;++i) if (ss[l+i-1]!=ss[(l+r)/2+i]) return false; return true;} int dp(int l,int r,int k){ if (fi[l][r][k]) return fi[l][r][k]; if (l==r) return fi[l][r][k]=1; int i,mn;mn=N; if (k) for (i=l;i<r;++i) mn=min(mn,dp(l,i,k)+1+dp(i+1,r,k)); for (i=l;i<r;++i) mn=min(mn,dp(l,i,k)+r-i); if (judge(l,r)) mn=min(mn,dp(l,(l+r)/2,0)+1); return fi[l][r][k]=mn; } int main(){ scanf("%s",ss+1);int n=strlen(ss+1); printf("%d",min(dp(1,n,0),dp(1,n,1))); }
bzoj3622 已经没有什么好害怕的了(!!!)
题目大意:已知两个数组ai、bi都有n个元素,求一一对应的满足:ai>bi比ai<bi恰好多k个的方案数。
思路:设fi[i][j]表示前i个数中至少有j个ai>bi的方案数。对ai、bi排序后,可以二分出每个ai所对应的最后一个满足ai>bi的bi的位置x,然后fi[i][j]=fi[i-1][j-1]*(x-(j-1))+fi[i-1][j]。然后要求恰好有s个(因为s+(s-k)=n,所以s=(n+k)/2,可以特判出0的情况)的答案,设gi[i]表示恰好有i个,gi[i]=fi[n][i]*(n-i)!-sigma(j=i+1~n) gi[j]*c(j,i) (不符合的方案数),最后gi[s]就是答案。
(还有一种容斥ans=fi[n][s]*(n-s)!-fi[n][s+1]*(n-s-1)!是错误的,不能保证n-s个都满足ai<bi的关系。)
orz sunshineorz sunshineorz sunshine
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define N 2005 #define p 1000000009LL #define LL long long using namespace std; int ai[N],bi[N]; LL fi[N][N]={0LL},gi[N],c[N][N]={0LL},jie[N]; int main(){ int n,k,i,j,ci;LL ans; scanf("%d%d",&n,&k); if ((n+k)%2) printf("0\n"); else{ for (c[0][0]=1LL,i=1;i<=n;++i) for (c[i][0]=1LL,j=1;j<=i;++j) c[i][j]=(c[i-1][j-1]+c[i-1][j])%p; for (jie[0]=1LL,i=1;i<=n;++i) jie[i]=jie[i-1]*(LL)i%p; for (i=1;i<=n;++i) scanf("%d",&ai[i]); for (i=1;i<=n;++i) scanf("%d",&bi[i]); sort(ai+1,ai+n+1);sort(bi+1,bi+n+1); fi[0][0]=1LL; for (i=1;i<=n;++i){ ci=upper_bound(bi+1,bi+n+1,ai[i])-bi-1; fi[i][0]=1LL; for (j=1;j<=i;++j) fi[i][j]=(fi[i-1][j]+(j ? fi[i-1][j-1]*(LL)max(ci-(j-1),0) : 0)%p)%p; }k=(n+k)>>1; for (i=n;i>=k;--i){ gi[i]=fi[n][i]*jie[n-i]%p; for (j=n;j>i;--j) gi[i]=((gi[i]-gi[j]*c[j][i]%p)%p+p)%p; }printf("%I64d\n",gi[k]); } }
bzoj1145 图腾(!!!)
题目大意:给定平面上n个点,横坐标为1~n,纵坐标是1~n的排列,求一些图形的个数。如果用1、2、3、4表示四个纵坐标的相对关系,令s1=1324,s2=1243,s3=1432,求s1-s2-s3的值。
思路:1324-1243-1432=(1*2*-1423)-(14**-1423)-(12**-1234)=1*2*-12**-14**+1234=1*2*-1***+13**+1234,所以我们要求这四项的值。设li[i]表示i左边比i小的数的个数,ri[i]表示i右边比i小的数的个数,可以树状数组预处理。
1)1234:枚举3,右边有(n-i-ri[i])个,左边是sigma(j=1~ai[i]-1)li[j](可以用树状数组求和)。sum=sigma(i=1~n) sigma(j=1~ai[i]-1)li[j]*(n-i-ri[i]);
2)1***:枚举1。sum=sigma(i=1~n)c(n-i-ri[i],3);
3)13**:枚举3,右边一定有一个4,有(n-i-ri[i])个,考虑固定2,1、2数对的个数是sigma(j>i,ai[j]<ai[i])ai[j](可以用树状数组求和),需要减掉同时在右边的情况(包含同时选了一个数),个数是ri[i]*(ri[i]+1)/2。sum=sigma(i=n~1)(sigma(j>i,ai[j]<ai[i])ai[j]-ri[i]*(ri[i]-1)/2)*(n-i-ri[i]);
4)1*2*:枚举2,右边一定比2大,有(n-i-n[i])个,考虑固定1(包含同时选一个的情况),有li[i]*(i-1)个,减掉都比2小并且1在左边的情况,有li[i]*(li[i]-1)/2个,还要减掉1在右边的情况,有sigma(j<i,ai[j]<ai[i])j个。sum=sigma(i=1~n)(li[i]*(i-1)-li[i]*(li[i]-1)/2-sigma(j<i,ai[j]<ai[i])j)*(n-i-ri[i])。
orz sunshineorz sunshineorz sunshine
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define N 200005 #define p 16777216 #define LL long long using namespace std; LL cc[N]; int ai[N],li[N],ri[N],n; int lowbit(int x){return x&(-x);} LL ask(int x){LL sum=0LL;for (;x;x-=lowbit(x)) sum+=cc[x];return sum;} void ins(int x,LL y){for (;x<=n;x+=lowbit(x)) cc[x]=(cc[x]+y)%p;} LL calc1(){ LL sum=0LL;int i; memset(cc,0,sizeof(cc)); for (i=1;i<=n;++i){ sum=(sum+ask(ai[i]-1)*(LL)(n-i-ri[i]))%p; ins(ai[i],(LL)li[i]); }return sum;} LL calc2(){ LL x,sum=0LL;int i; for (i=1;i<=n;++i){ if ((x=(LL)(n-i-ri[i]))<3LL) continue; sum=(sum+(LL)(x*(x-1LL)*(x-2LL)/6LL))%p; }return sum;} LL calc3(){ LL sum=0LL;int i; memset(cc,0,sizeof(cc)); for (i=n;i;--i){ sum=(sum+(ask(ai[i]-1LL)-(LL)(ri[i]*(ri[i]+1)/2))%p*(LL)(n-i-ri[i])%p+p)%p; ins(ai[i],(LL)ai[i]); }return sum;} LL calc4(){ LL sum=0LL;int i; memset(cc,0,sizeof(cc)); for (i=1;i<=n;++i){ sum=(sum+(li[i]*(LL)(i-1)-(LL)(li[i]*(li[i]-1)/2)-ask(ai[i]-1))*(LL)(n-i-ri[i])%p+p)%p; ins(ai[i],(LL)i); }return sum;} int main(){ int i,j;scanf("%d",&n); memset(cc,0,sizeof(cc)); for (i=1;i<=n;++i){ scanf("%d",&ai[i]); li[i]=(int)ask(ai[i]-1);ins(ai[i],1LL); ri[i]=ai[i]-li[i]-1; }printf("%I64d\n",((calc1()-calc2()+calc3()+calc4())%p+p)%p); }
bzoj2339 卡农
题目大意:将n个音阶放入m个集合,每个集合中每种音阶只能有一个,m个集合互不相同且非空,最终每个音阶的个数是偶数。
思路:题目要求无序,但先考虑有序,最后/m!(!!!)。设fi[i]表示长度为i的答案,首先有A(2^n-1,i-1)中方案(放完前i-1个,最后一个根据前面的进行调整),然后需要剪掉:1)第i个是空的,方案数是fi[i-1];2)第i个和前面的某个重复,有(i-1)个位置可能重复,可能的集合有(2^n-1-(i-2)),方案数是fi[i-2]*(i-1)*(2^n-1-(i-2))。所以fi[i]=A(2^n-1,i-1)-fi[i-1]-fi[i-2]*(i-1)*(2^n-1-(i-2))。
orz sunshineorz sunshineorz sunshine
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define LL long long #define p 100000007LL #define N 1000005 using namespace std; LL ai[N],fi[N]={0}; LL mi(LL x,int y){ LL a=1LL; for (;y;y>>=1){ if (y&1) a=a*x%p; x=x*x%p; }return a;} int main(){ int n,m,i,j;LL st,ss;scanf("%d%d",&n,&m); ss=st=(mi(2LL,n)-1LL+p)%p; ai[0]=1LL; for (i=1;i<=m;++i){ai[i]=ai[i-1]*st%p;st=(st+p-1)%p;} fi[1]=fi[2]=0LL; for (i=3;i<=m;++i) fi[i]=((ai[i-1]-fi[i-1]-fi[i-2]*(LL)(i-1)%p*(ss-(LL)(i-2))%p)%p+p)%p; for (ss=1LL,i=1;i<=m;++i) ss=ss*(LL)i%p; printf("%I64d\n",fi[m]*mi(ss,(int)p-2)%p); }
bzoj1974 代码拍卖会
题目大意:n位数(每位都是1~9),从左到右不减,问%p=0的个数。
思路:因为从左到右不减,所以是一些连续的11111、22222、......、99999(某个数字可以不出现),所以就是一些0、1、11、111、...、111...11中可重复选9个的和(其中至少有一个11111(n个1))(!!!)。这些数%p的值是循环的,所以可以处理出%p=i的个数gi[i],设fi[i][j][k]表示做到gi[i]选了j个%p=k的方案数,类似背包转移。
注意:1)找循环的时候不一定都能出现0,可能是0、1、1、1、...。p可能是1;
2)0要和1、11、111、...分开处理。
orz sunshineorz sunshineorz sunshine
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define N 505 #define p 999911659LL #define LL long long #define up 9 using namespace std; LL gi[N]={0},fi[N][up+1][N]={0LL},fac[N],inv[N],cc[N][up+1]; int mo[N]={0},vi[N]={0},lp[N]; LL mi(LL x,int y){ LL a=1LL; for (;y;y>>=1){ if (y&1) a=a*x%p; x=x*x%p; }return a;} LL getc(LL n,LL m){ LL i,ci=1LL; for (i=n;i<n+m;++i) ci=ci*(i%p)%p; return ci*inv[(int)m]%p;} void add(LL &x,LL y){x=(x+y)%p;} int main(){ LL n,ans=0LL;int i,j,k,la,a,b,m,amo; scanf("%I64d %d",&n,&m); for (fac[0]=1LL,i=1;i<=up;++i) fac[i]=fac[i-1]*(LL)i%p; for (inv[up]=mi(fac[up],(int)p-2),i=up-1;i>=0;--i) inv[i]=inv[i+1]*(LL)(i+1)%p; for (j=1,i=1%m;(LL)j<=n;i=(i*10+1)%m,++j){ if (vi[i]>=2) break; if (!vi[i]){++gi[i];lp[i]=j;} else{ gi[i]+=(n-(LL)lp[i])/(LL)(j-lp[i]); if ((n-(LL)lp[i])%(LL)(j-lp[i])==0) amo=i; }++vi[i]; }for (i=0;i<m;++i) for (j=0;j<=up;++j) cc[i][j]=getc(gi[i],(LL)j); fi[0][0][0]=1; if (gi[0]) for (i=1;i<up;++i) fi[0][i][0]=cc[0][i]; amo=(m-amo)%m; for (la=i=0;i<m;i=j){ j=i+1;while(j<m&&!gi[j]) ++j; if (j<m){ for (a=0;a<up;++a) for (b=up-a;b>=0;--b) for (k=0;k<m;++k) add(fi[j][a+b][(k+b*j)%m],fi[i][a][k]*cc[j][b]); la=j; } }for (i=0;i<9;++i) add(ans,fi[la][i][amo]); printf("%I64d\n",ans); }
bzoj3823 定情信物
题目大意:求n维超立方体的i维(0<=i<=n)元素个数的异或和。(0维是点,1维是线...)
思路:可以发现n维的0维元素个数是2^n,知道i维元素有x个之后可以推出i+1维元素有x*(n-i)/(2i+2)(考虑每个i维元素产生(n-i)个i+1维元素,而每个i+1维元素被算了2(i+1)次)。
注意:(1)乘和除的数可能是p的倍数,要把p的次方提出来单独保存,其他的用逆元之类的计算;
(2)逆元的时候也要%p;
(3)递推逆元inv[i]=p-(p/i)*inv[p%i]%p。
orz sunshineorz sunshineorz sunshine
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define N 10000005 #define LL long long using namespace std; int inv[N],pi=0,p; int fen(int x,int y){ while(x%p==0){x/=p;pi+=y;} return x;} int main(){ int k,x,ans,i,zi,mu; scanf("%d%d",&k,&p); for (inv[1]=x=1,i=2;i<=k;++i) inv[i]=p-(int)((LL)(p/i)*(LL)inv[p%i]%(LL)p); ans=x; for (i=k;i;--i){ zi=fen(i<<1,1); mu=fen(k-i+1,-1); x=(int)((LL)x*(LL)zi%p*(LL)inv[mu%p]%(LL)p); if (pi==0) ans^=x; }printf("%d\n",ans); }
bzoj1049 数字序列
题目大意:给定一个数列,把数列变成严格上升序列的最少改动个数和最少改动个数情况下最小改动幅度(每个数变化的绝对值之和)。
思路:第一问比较简单,fi[i]=min(ai[i]-ai[j]>=i-j)(fi[j]+1),如果先把每个数-i(bi=ai-i),相当于求最长不下降子序列,O(nlogn);数据随机,第二问可以暴力,对于最长不下降子序列中相邻两项i和j之间的数一定不再ai和aj之间,变后一定是在某个k之前和ai一样、k之后和aj一样,所以可以暴力n^3枚举。
orz sunshineorz sunshineorz sunshine
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define N 35005 #define LL long long using namespace std; int fi[N],tr[N<<2]={0},cz=0,point[N]={0},next[N]; LL ai[N],ci[N],gi[N],sm1[N],sm2[N]; LL ab(LL x){return (x<0LL ? -x : x);} void add(int u,int v){next[v]=point[u];point[u]=v;} int ask(int i,int l,int r,int ll,int rr){ if (ll<=l&&r<=rr) return tr[i]; int mid=(l+r)>>1;int mx=0; if (ll<=mid) mx=max(mx,ask(i<<1,l,mid,ll,rr)); if (rr>mid) mx=max(mx,ask(i<<1|1,mid+1,r,ll,rr)); return mx;} void tch(int i,int l,int r,int x,int y){ if (l==r){tr[i]=max(tr[i],y);return;} int mid=(l+r)>>1; if (x<=mid) tch(i<<1,l,mid,x,y); else tch(i<<1|1,mid+1,r,x,y); tr[i]=max(tr[i<<1],tr[i<<1|1]);} int main(){ int n,i,j,k;LL sm;scanf("%d",&n); for (i=1;i<=n;++i){ scanf("%I64d",&ai[i]); ai[i]-=(LL)i;ci[i]=ai[i]; }sort(ci+1,ci+n+1); cz=unique(ci+1,ci+n+1)-ci-1; for (i=1;i<=n;++i){ j=upper_bound(ci+1,ci+cz+1,ai[i])-ci-1; fi[i]=ask(1,1,n,1,j)+1; tch(1,1,n,j,fi[i]); }fi[0]=0;fi[n+1]=tr[1]+1; for (i=n;i;--i) add(fi[i],i); memset(gi,127,sizeof(gi)); for (i=1;i<=n+1;++i){ if (fi[i]==1){ for (sm=0LL,j=1;j<i;++j) sm+=ab(ai[j]-ai[i]); gi[i]=min(gi[i],sm); continue; }for (j=point[fi[i]-1];j;j=next[j]){ if (i>n) ai[i]=ai[j]; else{ if (j>i) break; if (ai[j]>ai[i]) continue; }sm1[j-1]=sm2[i+1]=0LL; for (k=j;k<=i;++k) sm1[k]=ab(ai[k]-ai[j])+sm1[k-1]; for (k=i;k>=j;--k) sm2[k]=ab(ai[i]-ai[k])+sm2[k+1]; for (k=j;k<i;++k) gi[i]=min(gi[i],gi[j]+sm1[k]+sm2[k+1]); } }printf("%d\n%I64d\n",n-tr[1],gi[n+1]); }
bzoj2665 编号(!!!)
题目大意:编号是7位的16进制数,要求两个编号之间至少三位不同,问第k小的编号是哪个。
思路:暴力枚举进制数,判断的时候:如果考虑三位,是C(7,3),关系是或(有一个没出现过),这样的常数比较大;可以考虑两位,是C(7,2),关系是且(都没出现过才可以)。
orz sunshineorz sunshineorz sunshine
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define N 200005 #define M 7 #define up 16 using namespace std; char gi[up]={'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'}; bool fi[21][up][up][up][up][up]={false}; int main(){ int k,a,b,c,d,e,f,g;scanf("%d",&k); for (a=0;a<up;++a) for (b=0;b<up;++b) for (c=0;c<up;++c) for (d=0;d<up;++d) for (e=0;e<up;++e) for (f=0;f<up;++f) for (g=0;g<up;++g) if (!fi[0][c][d][e][f][g]&&!fi[1][b][d][e][f][g]&& !fi[2][b][c][e][f][g]&&!fi[3][b][c][d][f][g]&& !fi[4][b][c][d][e][g]&&!fi[5][b][c][d][e][f]&& !fi[6][a][d][e][f][g]&&!fi[7][a][c][e][f][g]&& !fi[8][a][c][d][f][g]&&!fi[9][a][c][d][e][g]&& !fi[10][a][c][d][e][f]&&!fi[11][a][b][e][f][g]&& !fi[12][a][b][d][f][g]&&!fi[13][a][b][d][e][g]&& !fi[14][a][b][d][e][f]&&!fi[15][a][b][c][f][g]&& !fi[16][a][b][c][e][g]&&!fi[17][a][b][c][e][f]&& !fi[18][a][b][c][d][g]&&!fi[19][a][b][c][d][f]&& !fi[20][a][b][c][d][e]){ --k; if (!k){ printf("%c%c%c%c%c%c%c\n",gi[a],gi[b],gi[c],gi[d],gi[e],gi[f],gi[g]); return 0; } fi[0][c][d][e][f][g]=fi[1][b][d][e][f][g]= fi[2][b][c][e][f][g]=fi[3][b][c][d][f][g]= fi[4][b][c][d][e][g]=fi[5][b][c][d][e][f]= fi[6][a][d][e][f][g]=fi[7][a][c][e][f][g]= fi[8][a][c][d][f][g]=fi[9][a][c][d][e][g]= fi[10][a][c][d][e][f]=fi[11][a][b][e][f][g]= fi[12][a][b][d][f][g]=fi[13][a][b][d][e][g]= fi[14][a][b][d][e][f]=fi[15][a][b][c][f][g]= fi[16][a][b][c][e][g]=fi[17][a][b][c][e][f]= fi[18][a][b][c][d][g]=fi[19][a][b][c][d][f]= fi[20][a][b][c][d][e]=true; } }
这里通过且和或的转化可以优化常数的思路非常厉害。
bzoj2844 albus就是要第一个出场
题目大意:求子集异或和不去重排序后,x这个数最早出现的下标。
思路:考虑求出每一位只有一个1的线性基共m个,有n个数,一共有2^n个异或和,2^m种异或和,每一种异或和中:对于那n-m个不作为线性基的数是可以随便取的,并且可以通过组合得到不同的异或和。所以每种异或和出现次数都是2^(n-m)(!!!)。这样就可以数位dp出<x的异或种数,*2^(n-m)+1就是答案了。
(orz sunshineorz sunshineorz sunshine)
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define N 100005 #define up 31 #define p 10086 using namespace std; int in(){ char ch=getchar();int x=0; while(ch<'0'||ch>'9') ch=getchar(); while(ch>='0'&&ch<='9'){ x=x*10+ch-'0';ch=getchar(); }return x;} int ai[N],xj[up]={0},bi[up]={0}; int mi(int x,int y){ int a=1; for (;y;y>>=1){ if (y&1) a=a*x%p; x=x*x%p; }return a;} int main(){ int n,i,j,q,x,ans=0;n=in(); for (i=1;i<=n;++i){ ai[i]=in(); for (x=ai[i],j=up-1;j>=0;--j){ if (!((x>>j)&1)) continue; if (!xj[j]){xj[j]=x;break;} else x^=xj[j]; } }for (i=up-1;i>=0;--i) for (j=i-1;j>=0;--j) if ((xj[i]>>j)&1) xj[i]^=xj[j]; q=in(); for (i=j=0;i<up;++i) if (xj[i]) bi[i]=j++; for (i=up-1;i>=0;--i) if (((q>>i)&1)&&xj[i]){ q^=xj[i];ans|=(1<<bi[i]); } printf("%d\n",(ans%p*mi(2,n-j)+1)%p); }
bzoj3243 向量内积(!!!)
题目大意:给定n个向量,求出其中两个内积为k的倍数的方案。
思路:可以先求一个会出现在答案中的向量,然后就可以nd找到另一个。考虑k=2的情况,如果一个向量不会出现在答案中,就是这个向量点乘其他向量都是1,和其他向量的点积的和就是一个定值。对于k=3,不会出现的时候是1\2,平方后%k是1,所以可以把原向量平方之后变为d^2维向量,然后相乘。这样hash可能会出现一些问题,所以可以随机序列,然后多做几遍。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define N 100005 #define M 105 using namespace std; int in(){ char ch=getchar();int x=0; while(ch<'0'||ch>'9') ch=getchar(); while(ch>='0'&&ch<='9'){ x=x*10+ch-'0';ch=getchar(); }return x;} struct use{ int po,r; bool operator<(const use&x)const{return r<x.r;} }ai[N]; int xi[N][M],sm[M],sm1[M][M],gi[N],n,d,k; void add(int &x,int y){x+=y;if (x>=k) x%=k;} int calc(int x){ int i,j,ci=0; if (k==2){ for (i=1;i<=d;++i){ ci^=xi[x][i]&sm[i]; sm[i]^=xi[x][i]; } }else{ for (i=1;i<=d;++i) for (j=1;j<=d;++j){ add(ci,sm1[i][j]*xi[x][i]*xi[x][j]); add(sm1[i][j],xi[x][i]*xi[x][j]); } }return ci; } int getf(int x,int y){ int ci=0; for (int i=1;i<=d;++i) add(ci,xi[x][i]*xi[y][i]); return ci;} int main(){ int i,j;n=in();d=in();k=in(); for (i=1;i<=n;++i) for (j=1;j<=d;++j) xi[i][j]=in()%k; for (i=1;i<=n;++i) ai[i]=(use){i,rand()}; sort(ai+1,ai+n+1); for (i=1;i<=n;++i) gi[i]=ai[i].po; if (k==2) memset(sm,0,sizeof(sm)); else memset(sm1,0,sizeof(sm1)); for (i=1;i<=n;++i) if (calc(i)!=(i-1)%k){ for (j=1;j<=n;++j){ if (i==j) continue; if (!getf(i,j)){ printf("%d %d\n",min(i,j),max(i,j)); break; } }if (j<=n) break; } if (i>n) printf("-1 -1\n"); }