Heoi2014系列题解
Bzoj3609人人尽说江南好
显然我们要聚集起一个大小为m的石堆需要m - 1次的合并操作,我们可以把贡献单独拿出来看,我们将场上所有石子移动到无法移动的状态步数一定和把所有石子依次合并至上限的次数一样,因为。。。此处省略一千言。。 大概意会一下吧,感觉不好言传。。。大概是每次合并贡献实际是相同的这种,就像你算$x_1*x_2*x_3*...*x_n$的值一样,你没有办法通过改变乘的顺序减少乘的次数。
所以直接算出需要多少步,判一判奇偶输出答案。
代码 :
#include<bits/stdc++.h> #define INF 0x3f3f3f3f #define low(x) ((x)&(-(x))) #define LL long long #define eps 1e-9 using namespace std; #define int int inline int Max(int a,int b) {return a>b?a:b;} inline int Min(int a,int b) {return a<b?a:b;} inline int Abs(int a) {return a>0?a:-a;} inline int Sqr(int a) {return a*a;} #undef int int T,n,m,ans; int main() { scanf("%d",&T); while(T--) { scanf("%d%d",&n,&m); ans=(n/m)*(m-1);ans+=Max(n%m-1,0); printf("%d\n",ans&1^1); } return 0; }
Bzoj3610林中路径
如果要求的是$t$的和就直接一发矩阵快速幂就可以了,然而要求$t^2$的和。。。怎么办呢?我们可以维护多个矩阵。
设 t 为三个矩阵的集合
第一个矩阵 f 维护路径长度小于要求的路径的长度的平方和
第二个矩阵 g 维护长度小于要求的路径的个数
第三个矩阵 z 维护长度小于要求的路径的长度和
那么扩展的时候,假设t : i->j 有长为k1,k2,k3的三条路径,t' : j->k 有长度为k4,k5的两条路径,那么相乘的结果应该是如下的
F -> $(k1+k4)^2+(k1+k5)^2+(k2+k4)^2+...+(k3+k5)^2$ = $2*(k1^2+k2^2+k3^2)+3*(k4^2+k5^2)+2*(k1+k2+k3)*(k4+k5)$ = $g'*f+g*f'+2*z*z'$
G -> $g*g'$
Z -> $(k1+k4)+(k1+k5)+(k2+k4)+...+(k3+k5)$ = $2*(k1+k2+k3)+3*(k4+k5)$ = $g'*z+g*z'$
但是这样统计路径会有重复,我们发现重复是因为每条路径被分成两段有多种方案。
那么我们可以预处理出每个二的幂次长度路径的对应矩阵(路径长度一定为2的幂次而不是小于等于
那么我们就可以像数位dp一样统计
实际操作上我们对于长度为$(1011011)_2$的路径,我们去算 t[0,1000000]*t{0}+t[0,10000]*t{1000000}+t[0,1000]*t{1010000}+t[0,10]*t{1011000}+t[0,1]*t{1011010}
代码 :
//That's right ,I am killer . #include<bits/stdc++.h> #define MOD 1000000007 #define LL long long using namespace std; #define int int inline int Max(int a,int b) {return a>b?a:b;} inline int Min(int a,int b) {return a<b?a:b;} inline int Sqr(int a) {return a*a;} inline int Abs(int a) {return a>0?a:-a;} #undef int int n,m,q;LL k; struct Matrix { LL x[105][105]; void Init() { memset(x,0,sizeof(x)); } Matrix operator + (const Matrix &b) const { Matrix ret;ret.Init(); for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) ret.x[i][j]=(x[i][j]+b.x[i][j])%MOD; return ret; } Matrix operator * (const Matrix &b) const { Matrix ret; ret.Init(); for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) for(int k=1;k<=n;k++) ret.x[i][k]=(ret.x[i][k]+x[i][j]*b.x[j][k])%MOD; return ret; } }e; struct Trip{ Matrix f,g,z; void Init() { f.Init();g.Init();z.Init(); } Trip operator + (const Trip &b) const { Trip ret;ret.Init(); ret.f=f+b.f; ret.g=g+b.g; ret.z=z+b.z; return ret; } Trip operator * (const Trip &b) const { Trip ret;ret.Init(); ret.f=f*b.g+b.f*g+z*b.z+b.z*z; ret.g=g*b.g; ret.z=z*b.g+b.z*g; return ret; } }t[33],tp[33],re,ans; int main() { scanf("%d%d%lld%d",&n,&m,&k,&q); for(int a,b,i=1;i<=m;i++) { scanf("%d%d",&a,&b); t[0].f.x[a][b]=t[0].g.x[a][b]=t[0].z.x[a][b]=t[0].g.x[a][b]+1; } tp[0]=t[0];e.Init(); for(int i=1;i<=n;i++) e.x[i][i]=1; for(int i=1;(1<<i)<=k;i++) { t[i]=tp[i-1]*t[i-1]+t[i-1]; tp[i]=tp[i-1]*tp[i-1]; } ans.Init();re.Init();re.g=e; for(int i=32;~i;i--) if(k>>i&1) { ans=ans+re*t[i]; re=re*tp[i]; } for(int s,t,i=1;i<=q;i++) { scanf("%d%d",&s,&t); printf("%lld\n",ans.f.x[s][t]); } return 0; }
Bzoj3611大工程
问题实际上就是求树上给你一堆点,问两两之间的距离和,和距离的最值。显然可以树形dp去做,但是多组询问每次遍历全图dp可能会爆炸。这时就要请出我们的虚树了!
建完虚树dp就是了,虚树裸题。。。
有一点就是树形dp可以直接在建虚树时做完,因为建虚树的过程本身就是在dfs。
代码 :
//That's right ,I am killer . #include<bits/stdc++.h> #define LL long long #define INF 0x3f3f3f3f using namespace std; #define int int inline int Max(int a,int b) {return a>b?a:b;} inline int Min(int a,int b) {return a<b?a:b;} inline int Sqr(int a) {return a*a;} inline int Abs(int a) {return a>0?a:-a;} #undef int #define MAXN 1000006 struct Edge{ int next,to; }e[MAXN*2];int head[MAXN],cnt; inline void Insert(int a,int b) { e[++cnt].next=head[a];head[a]=cnt;e[cnt].to=b; e[++cnt].next=head[b];head[b]=cnt;e[cnt].to=a; } int dep[MAXN],dfn[MAXN],bln[MAXN],fa[MAXN],sz[MAXN]; inline bool cmp (const int &a,const int &b) { return dfn[a]<dfn[b]; } void Dfs(int v,int d) { dfn[v]=++dfn[0];dep[v]=d;sz[v]++; for(int i=head[v];i;i=e[i].next) { if(!dfn[e[i].to]) { Dfs(e[i].to,d+1); sz[v]+=sz[e[i].to]; fa[e[i].to]=v; } } } void HDfs(int v,int bl) { bln[v]=bl;int mx=0,mx_id=0; for(int i=head[v];i;i=e[i].next) if(!bln[e[i].to]&&sz[e[i].to]>mx) mx=sz[e[i].to],mx_id=e[i].to; if(mx_id) HDfs(mx_id,bl); for(int i=head[v];i;i=e[i].next) if(!bln[e[i].to]) HDfs(e[i].to,e[i].to); } int Lca(int u,int v) { while(bln[u]!=bln[v]) { if(dep[bln[u]]<dep[bln[v]]) swap(u,v); u=fa[bln[u]]; } return dep[u]>dep[v] ? v:u; } int n,q,k,p[MAXN]; int stk[MAXN],top,mx[MAXN],mi[MAXN],mians,mxans; LL sumans,sum[MAXN];int res[MAXN*2]; void Trans(int v,int t) { int len=dep[t]-dep[v]; sumans+=len*sum[t]*(k-sum[t]); sum[v]+=sum[t]; mxans=Max(mx[v]+mx[t]+len,mxans); mians=Min(mi[v]+mi[t]+len,mians); mx[v]=Max(mx[v],mx[t]+len); mi[v]=Min(mi[v],mi[t]+len); mx[t]=-INF;mi[t]=INF;sum[t]=0; } int main() { scanf("%d",&n); for(int a,b,i=1;i<n;i++) { scanf("%d%d",&a,&b); Insert(a,b); } Dfs(1,0);HDfs(1,1); scanf("%d",&q); for(int i=1;i<=n;i++) mi[i]=INF,mx[i]=-INF,sum[i]=0; for(int t=1;t<=q;t++) { scanf("%d",&k); for(int i=1;i<=k;i++) { scanf("%d",&p[i]); mx[p[i]]=0;mi[p[i]]=0;sum[p[i]]=1; } sort(p+1,p+k+1,cmp); top=0;mxans=0;mians=INF;sumans=0;res[0]=0; for(int lca,i=1;i<=k;i++) { lca=top ? Lca(stk[top],p[i]) : stk[top]; res[++res[0]]=lca;res[++res[0]]=p[i]; while(top>1&&dep[lca]<dep[stk[top-1]]) { Trans(stk[top-1],stk[top]); top--; } if(dep[lca]<dep[stk[top]]) { Trans(lca,stk[top--]); if(stk[top]!=lca) stk[++top]=lca; } stk[++top]=p[i]; } while(top>1) {Trans(stk[top-1],stk[top]);top--;} printf("%lld %d %d\n",sumans,mians,mxans); mx[stk[top]]=-INF;mi[stk[top]]=INF;sum[stk[top]]=0; } return 0; }
Bzoj3612平衡
实际上就是求一个数拆分乘1 ~ k个不同正整数且每个数大小不超过n的方案数。
设dp[n][k]为分成k个数,和为n的方案数
假设我们把所有数排成一列,这时我们把所有数都减少1
如果最小的数是1的话,那么我们就只有k-1个数和为n-k,对应dp[n-k][k-1]
否则我们就还有k个数和为n-k,对应dp[n-k][k]
所以dp[n][k]=dp[n-k][k-1]+dp[n-k][k]
不过还没完,我们分出的正整数是有上限的所以我们要从中扣除不合法状态的数量。
由于我们每一次转移相当与把所有数加1,所以不合法状态最大的数一定是lim+1
我们如果单独扣去最大的数,那么剩下数一定是合法状态且对应dp[n-(lim+1)][k-1]
所以我们最终的转移方程是dp[n][k]=dp[n-k][k-1]+dp[n-k][k]-dp[n-lim-1][k-1]
代码 :
//That's right ,I am killer . #include<bits/stdc++.h> #define LL long long #define INF 0x3f3f3f3f using namespace std; #define int int inline int Max(int a,int b) {return a>b?a:b;} inline int Min(int a,int b) {return a<b?a:b;} inline int Sqr(int a) {return a*a;} inline int Abs(int a) {return a>0?a:-a;} #undef int int n,k,T,p,ans; int dp[100005][15]; int main() { scanf("%d",&T); while(T--) { scanf("%d%d%d",&n,&k,&p); int lim=n*k;ans=0;dp[0][0]=1; for(int i=1;i<=lim;i++) { for(int j=1;j<=k;j++) { dp[i][j]= i>=j ? dp[i-j][j]+dp[i-j][j-1]:0; dp[i][j]-= i>=n+1 ? dp[i-n-1][j-1]:0; dp[i][j]=(dp[i][j]+p)%p; } } for(int i=1;i<=lim;i++) for(int j=1;j<=k;j++) ans=(ans+dp[i][j]*dp[i][k-j])%p; k--; for(int i=0;i<=lim;i++) for(int j=0;j<=k;j++) ans=(ans+dp[i][j]*dp[i][k-j])%p; printf("%d\n",ans); } return 0; }
Bzoj3613南园满地堆轻絮
二分答案,前面的数取到可取的最小值。然而nlogn跑5e6是不优雅的(虽然贼快
然后去看网上题解
线性做法:答案即为逆序对差值最大的一对的差值加1后除2。
正确性显然。。。。
代码 :(nlogn)
//That's right ,I am killer . #include<bits/stdc++.h> #define LL long long #define INF 0x3f3f3f3f using namespace std; #define int int inline int Max(int a,int b) {return a>b?a:b;} inline int Min(int a,int b) {return a<b?a:b;} inline int Sqr(int a) {return a*a;} inline int Abs(int a) {return a>0?a:-a;} #undef int #define MAXN 5000006 int n,ans,sa,sb,sc,sd,a[MAXN],MOD; LL F(int v) { LL ret=sd,tmp=v; (ret+=sc*tmp)%=MOD; (tmp*=v)%=MOD; (ret+=sb*tmp)%=MOD; (tmp*=v)%=MOD; (ret+=sa*tmp)%=MOD; return ret; } bool Check(int v) { int pre=a[1]-v; for(int i=2;i<=n;i++) { if(a[i]+v<pre) return 0; pre=Max(a[i]-v,pre); } return 1; } int main() { scanf("%d%d%d%d%d%d%d",&n,&sa,&sb,&sc,&sd,&a[1],&MOD); for(int i=2;i<=n;i++) a[i]=(F(a[i-1])+F(a[i-2]))%MOD; int l=0,r=1000000007,mid; while(l<=r) { mid=l+r>>1; if(Check(mid)) ans=mid,r=mid-1; else l=mid+1; } printf("%d",ans); return 0; }
Bzoj3614逻辑判断
我们将对应的值带入后解多元方程。
代码 :(递归解方程)
//That's right ,I am killer . #include<bits/stdc++.h> #define LL int #define eps 1e-9 #define INF 0x3f3f3f3f using namespace std; #define int int inline int Max(int a,int b) {return a>b?a:b;} inline int Min(int a,int b) {return a<b?a:b;} inline int Sqr(int a) {return a*a;} inline int Abs(int a) {return a>0?a:-a;} #undef int #define MAXN 1050000 inline int read() { int ret=0,f=1,re=2;char c=getchar(); while(c<'0'||c>'9') {if(c=='-') f=-f;c=getchar();} while(c>='0'&&c<='9') {ret=ret*10+c-'0';c=getchar();} if(c=='.') { c=getchar(); while(c>='0'&&c<='9') {ret=ret*10+c-'0';re--;c=getchar();} } while(re--) ret=ret*10; return ret*f; } int buf[15],bf; inline void out(int v) { if(v<0) {putchar('-');v=-v;} while(v) {buf[++bf]=v%10;v/=10;} while(bf) putchar(buf[bf--]+'0'); } int n; struct Frac{ LL s,m; Frac() {s=0;m=1;} inline void Refix() { LL t=Gcd(Abs(s),Abs(m)); s/=t;m/=t; if(m<0) m=-m,s=-s; } inline void Init(int a,int b) { s=a;m=b;Refix(); } inline void Out() { if(s==0) return; else if(m==1) out(s); else {out(s);putchar('/');out(m);} } inline LL Gcd(LL a,LL b) { LL t; while(b) {t=a%b;a=b;b=t;} return a; } inline LL Lcm(LL a,LL b) { return a/Gcd(a,b)*b; } Frac operator + (Frac x) { Frac ret; ret.m=Lcm(x.m,m); ret.s=ret.m/m*s+ret.m/x.m*x.s; ret.Refix(); return ret; } Frac operator * (Frac x) { Frac ret; ret.m=m/Gcd(m,x.s)*(x.m/Gcd(s,x.m)); ret.s=s/Gcd(s,x.m)*(x.s/Gcd(m,x.s)); ret.Refix(); return ret; } }; struct Pa{ int st;Frac v; }q[MAXN]; inline bool cmp(const Pa &a,const Pa &b) { return a.st<b.st; } char s[25]; void Solve(int l,int r) { if(l==r) return; int mid=l+r>>1;Frac a,b,c;c.Init(1,2); for(int i=mid-l;~i;i--) { a=q[l+i].v;b=q[mid+i+1].v; q[mid+i+1].v=(a+b)*c;a.s=-a.s; q[l+i].v=(a+b)*c; } Solve(l,mid);Solve(mid+1,r); } int stk[25],top;bool mk[MAXN]; void Ansout(int p,int l,int r) { if(l>r) return; if(!mk[r]) { q[r].v.Out(); if(q[r].v.s!=0) { if(top) putchar(' '); for(int i=1;i<=top;i++) {putchar('x');out(stk[i]);} printf("\n"); } mk[r]=1; } if(l==r) return; int mid=l+r>>1; stk[++top]=p;Ansout(p+1,l,mid); top--;Ansout(p+1,mid+1,r); } int main() { scanf("%d",&n); for(int i=1<<n;i;i--) { scanf("%s",s); q[i].v.Init(read(),100); for(int j=0;j<n;j++) if(s[n-j-1]=='+') q[i].st+=1<<j; } sort(q+1,q+(1<<n)+1,cmp); Solve(1,1<<n); Ansout(1,1,1<<n); return 0; }