HNOI2015 Day 2题解
昨天做了HNOI day 2,感觉好像还是可做的,想当年什么splay还是高级算法,现在点剖什么就老考了简直丧病,虽然第二题还没写那就先当下嘴巴选手吧= =
T1:[HNOI2015]落忆枫音
描述:http://www.lydsy.com/JudgeOnline/problem.php?id=4011
这道题还是挺神奇的,首先如果是个DAG的话我们可以把所有点的入度相乘就得到答案。
那么现在加上了一条边x->y,也就是说答案=原来的答案+用上这条边的答案,
我们可以发现,如果我们把x到根的链拿出来,其他点其实和DAG图上一样也可以入度直接相乘而得到答案,也就是S/prod(in[x])x为链上的点,可以发现如果记f[x]为所以到x的链上的1/prod(in[x])(也就是逆元啦)之和,我们可以用拓扑序来进行dp维护,就可以解决本题啦
我打的有点复杂,我是先把整个图的入度相乘然后再删去不合法的情况,也就是找y->x的链上的1/prod(in[x])之和,比较难打,但是总体思想差不多也就这样啦。
CODE:
1 #include<cstdio> 2 #include<iostream> 3 #include<cstring> 4 #include<algorithm> 5 using namespace std; 6 typedef long long ll; 7 #define maxn 200100 8 struct edges{ 9 int to,next; 10 }edge[maxn]; 11 int next[maxn],l; 12 ll f[maxn]; 13 inline void addedge(int x,int y) { 14 edge[++l]=(edges){y,next[x]};next[x]=l; 15 } 16 int q[maxn],in[maxn]; 17 inline void bfs() { 18 q[1]=1; 19 for (int l=1,r=1,u=1;l<=r;u=q[++l]) 20 for (int i=next[u];i;i=edge[i].next) { 21 in[edge[i].to]--; 22 if (in[edge[i].to]==0) q[++r]=edge[i].to; 23 } 24 } 25 #define mod 1000000007 26 inline int power(ll x,int y) { 27 ll ans=1; 28 for (;y;y>>=1) { 29 if (y&1) (ans*=x)%=mod; 30 (x*=x)%=mod; 31 } 32 return ans; 33 } 34 int n; 35 int a[maxn]; 36 inline void dp(int x,int y){ 37 for (int r=n,u=q[n];r;u=q[--r]) { 38 for (int i=next[u];i;i=edge[i].next) (f[u]+=f[edge[i].to])%=mod; 39 if (u==y) f[u]=1; 40 (f[u]*=power(a[u],mod-2))%=mod; 41 } 42 } 43 int main(){ 44 int m,x,y; 45 freopen("maple.in","r",stdin); 46 freopen("maple.out","w",stdout); 47 scanf("%d%d%d%d",&n,&m,&x,&y); 48 for (int i=1;i<=m;i++) { 49 int u,v; 50 scanf("%d%d",&u,&v); 51 addedge(u,v); 52 in[v]++; 53 } 54 if (y==1||x==y) { 55 ll ans=1; 56 for (int i=2;i<=n;i++) (ans*=in[i])%=mod; 57 printf("%lld\n",ans); 58 return 0; 59 } 60 for (int i=1;i<=n;i++) a[i]=in[i]; 61 a[y]++; 62 ll ans=1; 63 for (int i=2;i<=n;i++) (ans*=a[i])%=mod; 64 bfs(); 65 dp(y,x); 66 printf("%lld\n",(ans-ans*f[y]%mod+mod)%mod); 67 return 0; 68 } 69
T2:[HNOI2015]开店
描述:http://www.lydsy.com/JudgeOnline/problem.php?id=4012
又是CLJ的神题,数据结构一般都是被很多数据结构大神用各种方法秒,吾等蒟蒻只能被秒了= =
解法一:
首先有一种比较高端的做法,就是用线段树那样维护虚树,每个节点维护l~r的虚树(一共log n层每次直接nlogn暴力重构一共O(n log^2 n)),那么求答案可以在每棵虚数中单独求解。
接下来我们考虑如何求答案。对虚树上的节点分别记录子树中点的距离和down,子树外点的距离和up,以及子树中的节点数size。对于每一个查询,我们先找到在dfs序中比他前的点以及比他后的点,选择lca深度较大的点进行转移(因为这样才不会影响答案)。记从点x转移到点y,那么答案为down+up+(dist[y]-dist[x])*size-(dist[y]-dist[x])*(tot-size)(dist为该点的深度,tot为总节点树)
解法二:
我们可以先点剖一下然后考虑用可持久化线段树维护,对于每个点他至多被log n个重心覆盖,我们分别对每个重心ai进行查询,要求路径x,y必须穿过重心ai,记i的子树中l~r的节点数为count[i],dist[u]为u到重心的距离,sum[i]表示i子树中l~r的节点的dist之和,那么该子树ai的答案为count[ai]*dist[u]+sum[x]-count[y]*dis[u]-sum[y], 其中y为包含u的x的儿子节点,那么我们只需对重心已经他们的儿子节点维护一个线段树即可。
但是这样会爆空间,注意到每个点的儿子最多只有3个,那么我们可以直接维护他的儿子节点,不用维护其儿子节点即可
UPD:其实不用线段树的= =,开个vector记录子树节点然后排下序用前缀和,然后每次二分查询就行,感觉特别STL,初始化部分之间可以照搬CLJ ZJOI2015幻想乡战略游戏,几乎一模一样,写完不用3kb= =
两种做法时间均为O(N log^2 N)
CODE:
1 #include<cstdio> 2 #include<iostream> 3 #include<cstring> 4 #include<algorithm> 5 #include<vector> 6 using namespace std; 7 #define maxn 150100 8 typedef long long ll; 9 typedef pair<ll,ll> ii; 10 typedef pair<ll,ii> iii; 11 #define fi first 12 #define se second 13 #define pb push_back 14 struct edges{int to,next,dist;}edge[maxn*2]; 15 int l,next[maxn]; 16 inline void addedge(int x,int y,int z) { 17 edge[++l]=(edges){y,next[x],z};next[x]=l; 18 edge[++l]=(edges){x,next[y],z};next[y]=l; 19 } 20 bool done[maxn]; 21 int s[maxn],f[maxn],root,size; 22 void dfs(int u,int fa){ 23 s[u]=1;f[u]=0; 24 int v=0; 25 for (int i=next[u];i;i=edge[i].next) { 26 if (done[v=edge[i].to]||v==fa) continue; 27 dfs(v,u); 28 s[u]+=s[v]; 29 f[u]=max(f[u],s[v]); 30 } 31 f[u]=max(f[u],size-s[u]); 32 if (f[u]<f[root]) root=u; 33 } 34 vector<iii> pre[maxn]; 35 vector<ii> date[maxn][3]; 36 int a[maxn]; 37 void getdist(int u,int fa,int tar,int dist,int cnt) { 38 pre[u].pb(iii(tar,ii(dist,cnt))); 39 date[tar][cnt].pb(ii(a[u],dist)); 40 s[u]=1;int v=0; 41 for (int i=next[u];i;i=edge[i].next) { 42 if (done[v=edge[i].to]||v==fa) continue; 43 getdist(v,u,tar,dist+edge[i].dist,cnt); 44 s[u]+=s[v]; 45 } 46 } 47 void work(int u){ 48 done[u]=1; 49 int v=0; 50 pre[u].pb(iii(u,ii(0,3))); 51 int cnt=0; 52 for (int i=next[u];i;i=edge[i].next) { 53 if (done[v=edge[i].to]) continue; 54 getdist(v,0,u,edge[i].dist,cnt); 55 f[0]=size=s[v]; 56 dfs(v,root=0); 57 work(root); 58 cnt++; 59 } 60 } 61 ll query(vector<ii> *a,ii date,int l,int r) { 62 ll ans=0; 63 for (int i=0;i<=2;i++) { 64 if (i==date.se) continue; 65 int L=lower_bound(a[i].begin(),a[i].end(),ii(l,-1))-a[i].begin(), 66 R=lower_bound(a[i].begin(),a[i].end(),ii(r+1,-1))-a[i].begin(); 67 if (L) ans-=date.fi*L+a[i][L-1].se; 68 if (R) ans+=date.fi*R+a[i][R-1].se; 69 } 70 return ans; 71 } 72 int main(){ 73 freopen("shop.in","r",stdin); 74 freopen("shop.out","w",stdout); 75 int n,Q,_; 76 scanf("%d%d%d",&n,&Q,&_); 77 for (int i=1;i<=n;i++) scanf("%d",a+i); 78 for (int i=1;i<n;i++) { 79 int x,y,z; 80 scanf("%d%d%d",&x,&y,&z); 81 addedge(x,y,z); 82 } 83 f[0]=size=n; 84 dfs(1,root=0); 85 work(root); 86 for (int i=1;i<=n;i++) 87 for (int j=0;j<3;j++) { 88 sort(date[i][j].begin(),date[i][j].end()); 89 for (int k=1;k<date[i][j].size();k++) 90 date[i][j][k].se+=date[i][j][k-1].se; 91 } 92 ll ans=0; 93 while (Q--) { 94 int u,x,y; 95 scanf("%d%d%d",&u,&x,&y); 96 int l=min((ans+x)%_,(ans+y)%_),r=max((ans+x)%_,(ans+y)%_); 97 ans=0; 98 for (int i=0;i<pre[u].size();i++) { 99 if (l<=a[pre[u][i].fi]&&a[pre[u][i].fi]<=r) ans+=pre[u][i].se.fi; 100 ans+=query(date[pre[u][i].fi],pre[u][i].se,l,r); 101 } 102 printf("%lld\n",ans); 103 } 104 return 0; 105 }
T3:[HNOI2015]实验比较
描述:http://www.lydsy.com/JudgeOnline/problem.php?id=4013
首先对于这道题,我们可以发现如果u=v,那么我们可以将其两个点缩为同一个点,若u<v,那么我们可以连一条u向v的边,可以证明最后的图上每个联通块一定是一个环或者树。
如果是一个环的话答案显然为零。如果是一棵树的话,我们考虑使用树形dp,如果我们已知所有子树的答案,如何更新这棵树。
观察一个序列,我们可以发现该序列总是被若干个<分割成若干块,那么我们可以记f[i][j]为以i 为根节点的子树序列中有j个块的方案数一共有多少种,可得对两个不相干的树x,y结合的话,记答案为g[k],可得f[x][i],f[y][j]对g[k]的贡献为C(k,i)*C(i,i+j-k)*f[x][i]*f[y][j](可以这样认为,i个人先占据i个方格,那么剩下k-i个方格必须由j个人去占领,所以最后i+j-k个人就必须与之前的i个人一起了)
每次更新的时间复杂度为O(n^3),总的时间复杂度为O(n^4),可以通过这道题
CODE:
1 #include<cstdio> 2 #include<iostream> 3 #include<cstring> 4 #include<algorithm> 5 #include<vector> 6 using namespace std; 7 #define maxn 110 8 #define mod 1000000007 9 vector<int> e[maxn]; 10 #define pb push_back 11 int fa[maxn]; 12 int find(int x) {return fa[x]==x?fa[x]:fa[x]=find(fa[x]);} 13 int c[maxn][maxn]; 14 inline void init(){ 15 for (int i=0;i<=100;i++) { 16 c[i][0]=1; 17 for (int j=1;j<=i;j++) c[i][j]=(c[i-1][j-1]+c[i-1][j])%mod; 18 } 19 } 20 struct info{ 21 int level,f[maxn]; 22 info(){level=0;memset(f,0,sizeof(f));} 23 inline int sum(){ 24 int ans=0; 25 for (int i=1;i<=level;i++) (ans+=f[i])%=mod; 26 return ans; 27 } 28 }; 29 inline info update(info x,info y) { 30 info ans; 31 for (int i=1;i<=x.level;i++) { 32 for (int j=1;j<=y.level;j++) { 33 for (int k=max(i,j);k<=i+j;k++) { 34 (ans.f[k]+=c[k][i]*1ll*c[i][i+j-k]%mod*x.f[i]%mod*y.f[j]%mod)%=mod; 35 } 36 } 37 } 38 ans.level=x.level+y.level; 39 return ans; 40 } 41 bool b[maxn]; 42 info dfs(int x) { 43 info ans; 44 b[x]=1; 45 for (int i=0;i<e[x].size();i++) 46 if (i==0) ans=dfs(e[x][i]); 47 else ans=update(ans,dfs(e[x][i])); 48 for (int i=ans.level;i;i--) ans.f[i+1]=ans.f[i]; 49 ans.f[1]=ans.level==0; 50 ans.level++; 51 return ans; 52 } 53 54 int main(){ 55 freopen("pairwise.in","r",stdin); 56 freopen("pairwise.out","w",stdout); 57 int n,m; 58 init(); 59 scanf("%d%d",&n,&m); 60 for (int i=1;i<=n;i++) fa[i]=i; 61 static int from[maxn],to[maxn]; 62 static char opt[maxn][2]; 63 for (int i=1;i<=m;i++) { 64 scanf("%d%s%d",from+i,opt[i],to+i); 65 if (opt[i][0]=='=') fa[find(from[i])]=find(to[i]); 66 } 67 static int id[maxn],in[maxn]; 68 for (int i=1;i<=n;i++) id[i]=find(i); 69 for (int i=1;i<=m;i++) 70 if (opt[i][0]=='<') { 71 e[id[from[i]]].pb(id[to[i]]); 72 in[id[to[i]]]++; 73 } 74 info ans; 75 for (int i=1;i<=n;i++){ 76 if (!b[id[i]]&&in[id[i]]==0) ans=ans.level==0?dfs(id[i]):update(ans,dfs(id[i])); 77 } 78 for (int i=1;i<=n;i++) 79 if (!b[id[i]]) {printf("%d\n",0);return 0;} 80 printf("%d\n",ans.sum()); 81 return 0; 82 }