树的杂记
某次模擬賽的T2 強迫癥
題目大意:判斷一棵樹是否對稱。
思路:首先,一棵樹的重心一定在對稱軸上。一棵樹是否對稱有兩種情況:有點在對稱軸上和沒有點在對稱軸上。沒有點在對稱軸上的情況一定有兩個重心(但有兩個重心并不一定沒有點在對稱軸上)。有兩個重心時,我們只需要算出兩個重心對應子樹的hash值,然后比較,如果相等就對稱,否則就選其中一個重心(可以任選,并不影響結果)按一個重心的情況做一遍;有一個重心【可以有兩棵單獨的自己對稱的樹】(或上述再做一遍的情況【只能有一棵單獨的自己對稱的樹】)時,我們計算出這個重心所有子樹的hash值,放到map里,計算出hash值對應為奇數的hash值個數,如果超過了能單獨自己對稱樹的個數就不對稱,否則就判斷這棵樹能否自己對稱【同樣只能有一棵單獨的自己對稱的樹】,遞歸下去。
? ? ? ? ?考試的時候,窮舉了根,就會忽略沒有點在對稱軸上的情況,同時沒有想到很好的hash,所以就導致了分數不高。
? ? ? ? ?這道題目的hash的選擇要十分小心,很多基于子樹的hash如果拆開后可能會出現bug。所以在這道題中應用了位運算(異或是沒有分配律的,所以可以避免很多問題)。
#include<iostream> #include<cstdio> #include<cstring> #include<map> #include<cstdlib> #include<ctime> #define maxnode 10005 #define pp 233 using namespace std; int point[maxnode]={0},next[maxnode*2]={0},en[maxnode*2]={0},tot=0,root1,root2,maxsiz,n,siz[maxnode]={0},ha[30]={0}; char color[maxnode]={0}; map <unsigned long long,int> cnt,dui; void add(int u,int v) {++tot;next[tot]=point[u];point[u]=tot;en[tot]=v;++tot;next[tot]=point[v];point[v]=tot;en[tot]=u; } void getroot(int u,int fa) {int i,j,mm=0;siz[u]=1;for (i=point[u];i;i=next[i])if ((j=en[i])!=fa){getroot(j,u);siz[u]+=siz[j];mm=max(mm,siz[j]);}mm=max(mm,n-siz[u]);if (mm<=maxsiz){if (mm<maxsiz){maxsiz=mm;root1=u;root2=0;}else root2=u;} } unsigned long long gethash(int u,int fa) {int i,j;unsigned long long ss=0;for (i=point[u];i;i=next[i])if ((j=en[i])!=fa)ss+=gethash(j,u);return (ss*pp)^(ha[color[u]-'A']); } bool judge(int r1,int r2,int fa,int cn) {int i,j,x,y,cc=0,zhan[3]={0};unsigned long long ss,s1,s2;if (r2==0){cnt.clear();dui.clear();for (i=point[r1];i;i=next[i]){if ((j=en[i])==fa) continue;ss=gethash(j,r1);if (!cnt.count(ss)) cnt[ss]=0;++cnt[ss];dui[ss]=j;}map<unsigned long long,int>::iterator it;for (it=cnt.begin();it!=cnt.end();++it)if (it->second %2==1) {++cc;if (cc<=cn) zhan[cc]=dui[it->first];}if (cc>cn) return false;for (i=1;i<=cc;++i)if (!judge(zhan[i],0,r1,1)) return false;return true;}else{s1=gethash(r1,r2);s2=gethash(r2,r1);if (s1==s2) return true;return (judge(r1,0,0,2));} } int main() {freopen("tree.in","r",stdin);freopen("tree.out","w",stdout);int t,i,j,u,v;srand(time(0));for (i=0;i<26;++i) ha[i]=rand();scanf("%d",&t);while(t){scanf("%d",&n);tot=0;memset(point,0,sizeof(point));memset(next,0,sizeof(next));for (i=1;i<=n;++i)while(scanf("%c",&color[i])==1)if (color[i]>='A'&&color[i]<='Z') break;for (i=1;i<n;++i){scanf("%d%d",&u,&v);add(u,v);}maxsiz=n;getroot(1,root1=root2=0);if (judge(root1,root2,0,2)) printf("SYMMETRIC\n");else printf("NOT SYMMETRIC\n");--t;}fclose(stdin);fclose(stdout); }View Code
?
xjoi模擬賽?C
題目大意:給定一棵樹,葉子節點是蘋果,每個蘋果只能在一個時間范圍內采摘,一枝上的蘋果如果時間有交集可以一起采摘,問最少的次數。
思路:對于一個點的所有兒子,選出最靠右的左端點,那么比這個左端點小的右端點都要單獨摘,從這個左端點到它右面第一個右端點之間的時間是這個點可采摘范圍,傳給它的父親去決策。最后到根的時候看看如果沒摘完就+1。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define maxm 1000005 using namespace std; int fi[2][maxm],sz=0,point[maxm]={0},next[maxm]={0},ans=0; void build(int fa){int i,j,t,u;scanf("%d",&t);next[++sz]=point[fa];point[fa]=sz;if (!t){scanf("%d%d",&fi[0][sz],&fi[1][sz]);return;}for (u=sz,i=1;i<=t;++i) build(u);for (j=0,i=point[u];i;i=next[i]){++j;fi[0][u]=max(fi[0][i],fi[0][u]);}for (i=point[u];i;i=next[i]){if (fi[1][i]<fi[0][u]) ++ans;else fi[1][u]=min(fi[1][u],fi[1][i]);}if (!fa&&fi[1][u]>=fi[0][u]&&fi[1][u]<2100000000) ++ans; } int main(){memset(fi[0],128,sizeof(fi[0]));memset(fi[1],127,sizeof(fi[1]));build(0);printf("%d\n",ans); }View Code
?
bzoj2657 旅游
題目大意:給定一個n多邊形,n-2個城市都是三角形,且組成了多邊形的一種三角剖分,求最多能經過多少個城市(只能走頂點,且頂點不能相鄰,可以重復走)。
思路:一個城市一定有一條邊不是相鄰點間的邊(n=3時除外),且一條不是相鄰點的邊兩邊各有一個城市,對于有相鄰邊的三角形連邊,一定是一棵樹(凸多邊形的三角剖分不會在內部在分出一個點來),所以樹的直徑就是答案。考慮這個直徑個數是一定可取的,因為可以在原圖中的邊來切割樹邊(對偶圖);這個答案也是最大的,因為如果有更優的答案,也就是說從某一個城市可以在到一個城市,那么就在樹上有一條可以添入直徑的邊。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<map> #define maxm 200005 #define maxe 400005 using namespace std; struct use{int x,y;bool operator<(const use&xi)const{return (x==xi.x ? y<xi.y : x<xi.x);} }; int point[maxm]={0},en[maxe]={0},next[maxe]={0},ai[maxm][4],tot=0,fi[maxm]={0},ans=0; map <use,int>cnt; void add(int u,int v){if (!u||!v) return;next[++tot]=point[u];point[u]=tot;en[tot]=v;next[++tot]=point[v];point[v]=tot;en[tot]=u; } void dfs(int u,int fa){int i,j,v;fi[u]=1;for (i=point[u];i;i=next[i]){if ((v=en[i])==fa) continue;dfs(v,u);ans=max(ans,fi[v]+fi[u]);fi[u]=max(fi[u],fi[v]+1);} } int main(){int n,i,j;scanf("%d",&n);for (i=1;i<=n-2;++i){for (j=1;j<=3;++j) scanf("%d",&ai[i][j]);sort(ai[i]+1,ai[i]+4);add(i,cnt[(use){ai[i][1],ai[i][2]}]);add(i,cnt[(use){ai[i][1],ai[i][3]}]);add(i,cnt[(use){ai[i][2],ai[i][3]}]);cnt[(use){ai[i][1],ai[i][2]}]=i;cnt[(use){ai[i][1],ai[i][3]}]=i;cnt[(use){ai[i][2],ai[i][3]}]=i;}dfs(1,0);printf("%d\n",ans); }View Code
?
bzoj2282 消防&&bzoj1999 樹網的核
題目大意:給定一棵樹,求所有點到一段和不超過s的樹鏈的距離(距離的定義為點到鏈上點的最短距離)最大值的最小。
思路:這段樹鏈一定在某一條帶權直徑上(在直徑上是因為那樣到所有點的距離最小,任意一條直徑是因為每一條直徑上對應點到另一條直徑的距離都相等)。暴力可以枚舉鏈上的起點,然后二分出終點,利用之前dfs出來的所有點到鏈的距離(從鏈上每一個點向不再鏈上的點dfs,每個點只訪問一次)算出答案,這樣是n^2的。然后發現起點其實有三分性質(左端點向右走的時候,左邊是不減的;右端點向右走的時候,右邊是不增的;而位于鏈上的點可以同時看作右邊的)。
一開始三分完了之后每次都要對鏈上的點dfs求距離,非常慢,后來統一求了一次距離保存在鏈上更新過來的點上,對左邊、中間、右邊的點枚舉求出最大值+相應的值就可以了。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<ctime> #define maxm 500005 #define maxe 1000005 using namespace std; int point[maxm]={0},next[maxe]={0},en[maxe]={0},va[maxe]={0},n,s,tot=0,fi[maxm]={0},ne[maxm][2]={0},len=0,ansi=0,root=0,bi[maxm]={0},dd[maxm]={0},dis[maxm]={0}; bool visit[maxm]={false},li[maxm]={false}; int in(){int x=0;char ch=getchar();while(ch<'0'||ch>'9') ch=getchar();while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}return x; } void add(int u,int v,int vv){next[++tot]=point[u];point[u]=tot;en[tot]=v;va[tot]=vv;next[++tot]=point[v];point[v]=tot;en[tot]=u;va[tot]=vv; } void dfs(int u,int fa){int i,v,maxn=0,cmaxn=0;for (i=point[u];i;i=next[i]){if ((v=en[i])==fa) continue;dfs(v,u);if (fi[v]+va[i]>maxn){cmaxn=maxn;ne[u][1]=ne[u][0];maxn=fi[v]+va[i];ne[u][0]=v;}elseif (fi[v]+va[i]>cmaxn){cmaxn=fi[v]+va[i];ne[u][1]=v;}}if (cmaxn+maxn>len){len=cmaxn+maxn;ansi=u;}fi[u]=maxn; } void change(int u){if (ne[u][0]) {li[ne[u][0]]=true;change(ne[u][0]);}else root=u;if (u==ansi&&ne[u][1]) {li[ne[u][1]]=true;change(ne[u][1]);} } void dfs2(int u,int fa){int i,v;bi[++bi[0]]=u;for (i=point[u];i;i=next[i]){if ((v=en[i])==fa) continue;if (li[v]){dd[bi[0]+1]=dd[bi[0]]+va[i];dfs2(v,u);}} } void dfs3(int u,int anc,int dep){int i,v;visit[u]=true;if (dep>dis[anc]) dis[anc]=dep;for (i=point[u];i;i=next[i])if (!visit[v=en[i]]) dfs3(v,anc,dep+va[i]); } int calc(int x){int i,j=0,ans=0;j=upper_bound(dd+1,dd+bi[0]+1,dd[x]+s)-dd-1;for (ans=0,i=1;i<x;++i) ans=max(ans,dis[i]+dd[x]-dd[i]);for (i=x;i<=j;++i) ans=max(ans,dis[i]);for (i=j+1;i<=bi[0];++i) ans=max(ans,dis[i]+dd[i]-dd[j]);return ans; } int work(){int l,r,m1,m2;l=1;r=bi[0];while(l<r){if (r-l>=3){m1=l+(r-l)/3;m2=r-(r-l)/3;}else{return min(calc(l),min(calc(r),(l<r-1 ? calc(l+1) : 0)));}if (calc(m1)<calc(m2)) r=m2;else l=m1;}return calc(l); } int main(){int i,u,v,vv;n=in();s=in();for (i=1;i<n;++i){u=in();v=in();vv=in();add(u,v,vv);}dfs(1,0);li[ansi]=true;change(ansi);dfs2(root,0);for (i=1;i<=bi[0];++i) visit[bi[i]]=true;for (i=1;i<=bi[0];++i) dfs3(bi[i],i,0);printf("%d\n",work()); }View Code
?
bzoj3124 直徑
題目大意:給定一棵樹,求樹的直徑長度和出現在所有直徑上的邊的條數。
思路:一棵樹的所有直徑至少交于一個點,同時所有直徑的交一定是連續的部分。所以可以找出一條直徑,在直徑上判斷那些是可行的部分,如果直徑上一點i不經過直徑上的點到直徑外的最遠距離和直徑的某個端點x一樣,則i~x的邊都不會出現在所有直徑上。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define N 200005 #define M 400005 #define LL long long using namespace std; int point[N]={0},next[M],en[M],tot=0,zh[N],fb[N]={0},pre[N]; LL va[M],len,dis[N],dd[N]; void add(int u,int v,LL vv){next[++tot]=point[u];point[u]=tot;en[tot]=v;va[tot]=vv;next[++tot]=point[v];point[v]=tot;en[tot]=u;va[tot]=vv;} void gdis(int u,int fa,int k){int i,v;pre[u]=fa;if (dis[u]>len){len=dis[u];if (!k) zh[zh[0]=1]=u;}for (i=point[u];i;i=next[i]){if ((v=en[i])==fa||fb[v]) continue;dis[v]=dis[u]+va[i];gdis(v,u,k);} } int main(){int n,i,u,v,l,r;LL vv,ml;scanf("%d",&n);for (i=1;i<n;++i){scanf("%d%d%I64d",&u,&v,&vv);add(u,v,vv);}len=dis[1]=0LL;gdis(1,0,0);len=dis[zh[1]]=0LL;gdis(zh[1],0,0);for (i=1;pre[zh[i]];++i) zh[++zh[0]]=pre[zh[i]];for (i=1;i<=zh[0];++i){dd[zh[i]]=dis[zh[i]];fb[zh[i]]=1;}ml=len;printf("%I64d\n",len);l=1;r=zh[0];for (i=1;i<=zh[0];++i){len=dis[zh[i]]=0LL;gdis(zh[i],0,1);if (len==ml-dd[zh[i]]) l=i;if (len==dd[zh[i]]){r=i;break;}}printf("%d\n",r-l); }View Code
?
bzoj3162 獨釣寒江雪
題目大意:給定一棵樹,求本質不同的獨立集的個數。(本質不同指不能通過重新編號使得樹的結構相同且選定的點相同)
思路:首先要能找到結構相同的子樹,所以可以找到樹的重心之后hash,因為重心可能有兩個,所以把邊拆點,這樣重心一定只有一個(可能是點也可能是邊)。考慮樹型dp,fi[i][0]表示不選i這個點的方案數,fi[i][1]表示選i這個點的方案數,對于不是重心的邊i,一定只有一個兒子,所以fi[i]=fi[soni];對于是重心的邊i,兩個兒子不能同時選,所以是都不選的方案(cc(fi[soni][0],2),相當于一共有n種方案,選m個的可重組合cc(n,m)=c(n+m-1,m))+選一個的(fi[soni][0]*fi[soni][1]);對于結構一樣的子樹的方案fi[i][1]=∏(結構不同的j)cc(fi[j][0],sizj)(設sizj是i的子樹中和j結構一樣的子樹個數),fi[i][0]=∏(結構不同的j)cc(fi[j][0]+fi[j][1],sizj)。如果重心是邊,答案是fi[i][0];如果重心是點,答案是fi[i][0]+fi[i][1]。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define N 1000005 #define M 2000005 #define p 1000000007LL #define pp 233LL #define LL long long #define UL unsigned long long using namespace std; int point[N]={0},next[M],en[M],tot=0,rt,mn,siz[N]={0},n,zh[N]; LL fi[N][2],ci[N]; UL hv[N],rv[N]; void add(int u,int v){next[++tot]=point[u];point[u]=tot;en[tot]=v;next[++tot]=point[v];point[v]=tot;en[tot]=u;} void find(int u,int fa){int i,v,mz=0;siz[u]=1;for (i=point[u];i;i=next[i]){if ((v=en[i])==fa) continue;find(v,u);siz[u]+=siz[v];mz=max(mz,siz[v]);}mz=max(mz,n*2-1-siz[u]);if (mz<mn){rt=u;mn=mz;} } void geth(int u,int fa){int i,v;siz[u]=1;hv[u]=0;for (i=point[u];i;i=next[i]){if ((v=en[i])==fa) continue;geth(v=en[i],u);siz[u]+=siz[v];hv[u]+=hv[v];}hv[u]=(hv[u]*p)^rv[siz[u]];} int cmp(int x,int y){return hv[x]<hv[y];} 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,ans;ci=ans=1LL;n=n+m-1;for (i=n-m+1;i<=n;++i) ans=ans*i%p;for (i=1LL;i<=m;++i) ci=ci*i%p;return ans*mi(ci,(int)(p-2LL))%p;} void dp(int u,int fa){int i,j,v;LL cc;fi[u][0]=fi[u][1]=1LL;for (i=point[u];i;i=next[i]){if ((v=en[i])==fa) continue; dp(v,u);if (u>n&&u!=rt){fi[u][0]=fi[v][0];fi[u][1]=fi[v][1];}}if (u>n&&u!=rt) return;for (zh[0]=0,i=point[u];i;i=next[i]){if ((v=en[i])==fa) continue;zh[++zh[0]]=v;}sort(zh+1,zh+zh[0]+1,cmp);for (i=1;i<=zh[0];i=j+1){j=i;while(j<zh[0]&&hv[zh[j+1]]==hv[zh[j]]) ++j;if (u>n&&u==rt)fi[u][0]=fi[u][0]*(getc(fi[zh[i]][0],(LL)(j-i+1))+fi[zh[i]][0]*fi[zh[i]][1]%p)%p;else{fi[u][0]=fi[u][0]*getc(fi[zh[i]][0]+fi[zh[i]][1],(LL)(j-i+1))%p;fi[u][1]=fi[u][1]*getc(fi[zh[i]][0],(LL)(j-i+1))%p;}} } int main(){int i,j,u,v;scanf("%d",&n);for (i=1;i<=n;++i) rv[i]=(UL)rand();for (i=1;i<n;++i){scanf("%d%d",&u,&v);add(u,i+n);add(v,i+n);}mn=2*n-1;find(1,0);geth(rt,0);dp(rt,0);printf("%I64d\n",(rt>n ? fi[rt][0] : fi[rt][0]+fi[rt][1])%p); }View Code
?
bzoj4424 Fairy
題目大意:給定一張無向圖,問刪掉那些邊能使原圖是二分圖(每次只刪掉這一條邊)。
思路:有奇環的時候就不是二分圖。先dfs出一棵樹(這樣保證了非樹邊都是返祖邊),二分圖染色。1)如果只有樹邊所有邊都可以刪;2)如果只有一條非樹邊,這條邊可以刪;3)有非樹邊的時候,只由那些被所有能構成奇環的非樹邊經過且不被能構成偶環的樹邊可以刪掉。統計每條邊被什么邊經過可以在樹上查分,前綴和一下,然后判斷(注意重邊自環的處理)。
有點類似bzoj4025,不過這題的要求更少,所以有復雜度更優的算法。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define N 1000005 #define M 2000005 using namespace std; struct use{int v,id;}ed[M]; int point[N]={0},next[M],tot=0,fi[N]={0},gi[N]={0},dep[N]={0},zh[N],fa[N]={0},co[N]={0}; bool ans[M]={false},vi[N],vv[M]={false}; 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;} void add(int u,int v,int x){next[++tot]=point[u];point[u]=tot;ed[tot]=(use){v,x};next[++tot]=point[v];point[v]=tot;ed[tot]=(use){u,x};} void pre(int u,int ff){int i,j,v;vi[u]=true;zh[++zh[0]]=u;co[u]=co[ff]^1;dep[u]=dep[ff]+1;for (i=point[u];i;i=next[i]){if (vi[v=ed[i].v]) continue;fa[v]=i;vv[i]=true;pre(v,u);} } void dfs(int u,int ff){int i,j,v;vi[u]=true;for (i=point[u];i;i=next[i]){if (vi[v=ed[i].v]) continue;dfs(v,u);fi[u]+=fi[v];gi[u]+=gi[v];} } int main(){int i,j,k,n,m,u,v,cnt=0,po,cn=0;n=in();m=in();for (i=1;i<=m;++i){u=in();v=in();add(u,v,i);}for (i=1;i<=n;++i){if (vi[i]) continue;zh[0]=0;pre(i,0);for (j=1;j<=zh[0];++j){for (k=point[zh[j]];k;k=next[k]){if (dep[ed[k].v]==dep[zh[j]]){++cn;po=k;}if (dep[ed[k].v]<=dep[zh[j]]||vv[k]) continue;if (co[ed[k].v]==co[zh[j]]){++fi[ed[k].v];--fi[zh[j]];++cnt;po=k;}else{++gi[ed[k].v];--gi[zh[j]];}}vi[zh[j]]=false;}dfs(i,0);}cnt+=cn/2;if (!cnt){for (i=1;i<=n;++i)for (j=point[i];j;j=next[j]) ans[ed[j].id]=true;}else{if (cnt==1) ans[ed[po].id]=true;for (i=1;i<=n;++i)if (fi[i]==cnt&&!gi[i]) ans[ed[fa[i]].id]=true;}for (cnt=0,i=1;i<=m;++i)if (ans[i]) ++cnt;printf("%d\n",cnt);for (i=1;i<=m;++i)if (ans[i]) printf("%d ",i);printf("\n"); }View Code
?
bzoj4238 電壓
題目大意:通過改變不同點的顏色,使得去掉一條邊之后原圖是二分圖,問總共有多少條邊可以被刪。
思路:同上一題,但要把沒有奇環的時候那些偶環上的邊判掉。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define N 100005 #define M 400005 using namespace std; struct use{int v,id;}ed[M]; int point[N]={0},next[M],tot=0,fi[N]={0},gi[N]={0},dep[N]={0},zh[N],fa[N]={0},co[N]={0}; bool ans[M]={false},vi[N],vv[M]={false}; 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;} void add(int u,int v,int x){next[++tot]=point[u];point[u]=tot;ed[tot]=(use){v,x};next[++tot]=point[v];point[v]=tot;ed[tot]=(use){u,x};} void pre(int u,int ff){int i,v;vi[u]=true;zh[++zh[0]]=u;co[u]=co[ff]^1;dep[u]=dep[ff]+1;for (i=point[u];i;i=next[i]){if (vi[v=ed[i].v]) continue;fa[v]=i;vv[i]=true;pre(v,u);} } void dfs(int u){int i,v;vi[u]=true;for (i=point[u];i;i=next[i]){if (vi[v=ed[i].v]) continue;dfs(v);fi[u]+=fi[v];gi[u]+=gi[v];} } int main(){int i,j,k,n,m,u,v,cnt=0,po,cn=0;n=in();m=in();for (i=1;i<=m;++i){u=in();v=in();add(u,v,i);}for (i=1;i<=n;++i){if (vi[i]) continue;zh[0]=0;pre(i,0);for (j=1;j<=zh[0];++j){for (k=point[zh[j]];k;k=next[k]){if (dep[ed[k].v]==dep[zh[j]]){++cn;po=k;}if (dep[ed[k].v]<=dep[zh[j]]||vv[k]) continue;if (co[ed[k].v]==co[zh[j]]){++fi[ed[k].v];--fi[zh[j]];++cnt;po=k;}else{++gi[ed[k].v];--gi[zh[j]];}}vi[zh[j]]=false;}dfs(i);}cnt+=cn/2;if (!cnt){for (i=1;i<=n;++i)for (j=point[i];j;j=next[j])if (dep[ed[j].v]>dep[i]&&!gi[ed[j].v]) ans[ed[j].id]=true;}else{if (cnt==1) ans[ed[po].id]=true;for (i=1;i<=n;++i)if (fi[i]==cnt&&!gi[i]) ans[ed[fa[i]].id]=true;}for (cnt=0,i=1;i<=m;++i)if (ans[i]) ++cnt;printf("%d\n",cnt); }View Code
?
虛樹
bzoj2286 消耗戰
題目大意:給定一棵樹,邊有代價,每次操作有一些點上有東西,根是1,要求所有有東西的點和根都不能相連,求割掉邊的最小代價。
思路:虛樹的一道模板題,可以把代價給點,維護點到根的最小值就可以了。建立虛樹的時候一直維護最右鏈,然后不斷的操作。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define maxnode 500005 #define LL long long #define inf 0x7fffffffffffffffLL #define up 30 using namespace std; struct edge{int st,en,next;LL va; }ai[maxnode]={0},bi[maxnode]={0}; int id[maxnode]={0},ti=0,point[maxnode]={0},tot=0,hi[maxnode]={0},fa[maxnode][up],dep[maxnode]={0},zh[maxnode]={0}; LL fi[maxnode]={0},rd[maxnode]={0}; int cmp(int x,int y){return id[x]<id[y];} void add(int u,int v,LL w) {ai[++tot].next=point[u];point[u]=tot;ai[tot].en=v;ai[tot].va=w;ai[++tot].next=point[v];point[v]=tot;ai[tot].en=u;ai[tot].va=w; } void ad(int u,int v) {if (u==v) return;bi[++tot].next=point[u];point[u]=tot;bi[tot].en=v; } void dfs(int u,int f) {int i,j;id[u]=++ti;dep[u]=dep[f]+1;fa[u][0]=f;for (i=1;i<up;++i) fa[u][i]=fa[fa[u][i-1]][i-1];for (i=point[u];i;i=ai[i].next)if ((j=ai[i].en)!=f){rd[j]=min(ai[i].va,rd[u]);dfs(j,u);} } int lca(int x,int y) {int i,j;if (dep[x]<dep[y]) swap(x,y);for (i=up-1;i>=0;--i)if (dep[fa[x][i]]>=dep[y]) x=fa[x][i];if (x==y) return x;for (i=up-1;i>=0;--i)if (fa[x][i]!=fa[y][i]){x=fa[x][i];y=fa[y][i];}return fa[x][0]; } void dp(int u) {int i,j;LL sum=0;for (i=point[u];i;i=bi[i].next){dp(j=bi[i].en);sum+=fi[j];}point[u]=0;if (sum==0) fi[u]=rd[u];else fi[u]=min(rd[u],sum); } int main() {int n,i,j,u,v,m,t,top=0,k;LL w;scanf("%d",&n);for (i=1;i<n;++i){scanf("%d%d%I64d",&u,&v,&w);add(u,v,w);}rd[1]=inf;dfs(1,0);scanf("%d",&m);memset(point,0,sizeof(point));while(m--){scanf("%d",&k);zh[top=1]=1;tot=0;for (i=1;i<=k;++i) scanf("%d",&hi[i]);sort(hi+1,hi+k+1,cmp);hi[t=1]=hi[1];for (i=2;i<=k;++i)if (lca(hi[t],hi[i])!=hi[t]) hi[++t]=hi[i];for (i=1;i<=t;++i){u=lca(hi[i],zh[top]);while(1){if (dep[u]>=dep[zh[top-1]]){ad(u,zh[top--]);if (zh[top]!=u) zh[++top]=u;break;}ad(zh[top-1],zh[top]);--top;}if (hi[i]!=zh[top]) zh[++top]=hi[i];}for (i=top;i>1;--i) ad(zh[i-1],zh[i]);dp(1);printf("%I64d\n",fi[1]);} }View Code
?
bzoj3611 大工程
題目大意:給定一棵樹,邊權為1。有多次操作求某些點兩兩距離的總和、最大值、最小值。
思路:建出虛樹之后,dp更新一下。(注意數組不能memset,用多少清多少;注意long long)
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define maxm 2000005 #define up 20 #define LL long long #define inf 0x7fffffffffffffffLL using namespace std; int point[maxm]={0},next[maxm]={0},en[maxm],point1[maxm]={0},next1[maxm]={0},en1[maxm],tot=0,fa[maxm][up+1]={0},zhan[maxm]={0},que[maxm]={0},id[maxm]={0},tt=0; LL ans,maxn,minn,siz[maxm]={0},dep[maxm]={0},sum[maxm],mal[maxm],mil[maxm]; bool visit[maxm]={false}; int cmp(int x,int y){return id[x]<id[y];} void add(int u,int v){next[++tot]=point[u];point[u]=tot;en[tot]=v;next[++tot]=point[v];point[v]=tot;en[tot]=u; } void add2(int u,int v){if (u==v) return;next1[++tot]=point1[u];point1[u]=tot;en1[tot]=v; } void pre(int u,int ff){int i,j,v;fa[u][0]=ff;dep[u]=dep[ff]+1LL;id[u]=++tt;for (i=1;i<=up;++i) fa[u][i]=fa[fa[u][i-1]][i-1];for (i=point[u];i;i=next[i])if (!dep[v=en[i]]) pre(v,u); } int lca(int u,int v){int i,j;if (dep[u]<dep[v]) swap(u,v);for (i=up;i>=0;--i)if (dep[fa[u][i]]>=dep[v]) u=fa[u][i];if (u==v) return u;for (i=up;i>=0;--i)if (fa[u][i]!=fa[v][i]){u=fa[u][i];v=fa[v][i];}return fa[u][0]; } void dp(int u){LL maxl=0,minl;int i,j,v;minl=inf/3;sum[u]=mal[u]=0;mil[u]=(visit[u]?0:inf)/3;siz[u]=(visit[u]?1:0);for (i=point1[u];i;i=next1[i]){dp(v=en1[i]);ans+=sum[u]*siz[v]+(sum[v]+(dep[v]-dep[u])*siz[v])*siz[u];siz[u]+=siz[v];sum[u]+=sum[v]+(dep[v]-dep[u])*siz[v];maxl=max(maxl,mal[u]+dep[v]-dep[u]+mal[v]);mal[u]=max(mal[u],dep[v]-dep[u]+mal[v]);minl=min(minl,mil[u]+dep[v]-dep[u]+mil[v]);mil[u]=min(mil[u],dep[v]-dep[u]+mil[v]);}maxn=max(maxn,maxl);minn=min(minn,minl);point1[u]=0;visit[u]=false; } int main(){int n,i,j,u,v,q,k,top,tail;scanf("%d",&n);for (i=1;i<n;++i){scanf("%d%d",&u,&v);add(u,v);}pre(1,0);scanf("%d",&q);for (i=1;i<=q;++i){for (j=1;j<=tot;++j) next1[j]=0;scanf("%d",&k);v=tot=0;for (j=1;j<=k;++j){scanf("%d",&que[j]);visit[que[j]]=true;v=(!v ? que[j] : lca(v,que[j]));}sort(que+1,que+k+1,cmp);zhan[top=1]=v;for (j=1;j<=k;++j){u=lca(zhan[top],que[j]);while(1){if (dep[u]>=dep[zhan[top-1]]){add2(u,zhan[top--]);if (zhan[top]!=u) zhan[++top]=u;break;}add2(zhan[top-1],zhan[top]);--top;}if(que[j]!=zhan[top]) zhan[++top]=que[j];}for (top;top>1;--top) add2(zhan[top-1],zhan[top]);ans=maxn=0;minn=inf;dp(v);printf("%I64d %I64d %I64d\n",ans,minn,maxn);} }View Code
?
bzoj3572 世界樹(!!!)
題目大意:給定一棵樹,每次詢問設置一些特殊點,其他點選擇最近的(距離一樣就選編號小的)特殊點,問每個特殊點被選擇的數量(包括自身)。
思路:建立虛樹,然后兩遍dp出虛樹上每個點選擇的特殊點(一次從下到上,一次從上到下),這些特殊點外側的點的個數都可以算出來。然后要考慮虛樹上一條邊所代表的那些點以及他們不在虛樹上的子樹,可以用倍增的方法,二分出一條鏈上的mid,然后mid之上的更新給上面,之下的給下面。
注意:1)式子;2)向下dp的時候不僅考慮父親,還要考慮考慮兄弟的更新。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define N 600005 #define M 600005 #define inf 1000000000 #define uu 20 using namespace std; int fa[N][uu]={0},dep[N]={0},dt[N]={0},tt=0,siz[N],point[N]={0},next[M],en[M],tot,po1[N]={0},ne1[N],en1[N],que[N],top,zh[N],fi[N]={0},ans[N]={0},hi[N]; bool vi[N]={false}; int cmp(int x,int y){return dt[x]<dt[y];} void add(int u,int v){next[++tot]=point[u];point[u]=tot;en[tot]=v;next[++tot]=point[v];point[v]=tot;en[tot]=u;} void add1(int u,int v){if (u==v) return;ne1[++tot]=po1[u];po1[u]=tot;en1[tot]=v;} void pre(int u,int ff){int i,v;dt[u]=++tt;siz[u]=1;dep[u]=dep[ff]+1;fa[u][0]=ff;for (i=1;i<uu;++i) fa[u][i]=fa[fa[u][i-1]][i-1];for (i=point[u];i;i=next[i]){if ((v=en[i])==ff) continue;pre(v,u);siz[u]+=siz[v];} } int lca(int u,int v){int i;if (dep[u]<dep[v]) swap(u,v);for (i=uu-1;i>=0;--i)if (dep[fa[u][i]]>=dep[v]) u=fa[u][i];if (u==v) return u;for (i=uu-1;i>=0;--i)if (fa[u][i]!=fa[v][i]){u=fa[u][i];v=fa[v][i];}return fa[u][0]; } int dis(int x,int y){return dep[x]+dep[y]-2*dep[lca(x,y)];} void up(int u,int ff){int i,v,d1,d2;for (i=po1[u];i;i=ne1[i]){if ((v=en1[i])==ff) continue; up(v,u);d1=dis(fi[v],u);if (fi[u]) d2=dis(fi[u],u);if ((!fi[u])||(d1<d2)||(d1==d2&&fi[v]<fi[u])) fi[u]=fi[v];}if (vi[u]) fi[u]=u; } void down(int u,int ff){int i,v,d1,d2;for (i=po1[u];i;i=ne1[i]){if ((v=en1[i])==ff) continue;d1=dis(fi[u],v);d2=dis(fi[v],v);if ((d1<d2)||(d1==d2&&fi[u]<fi[v])) fi[v]=fi[u];down(v,u);}ans[fi[u]]+=siz[u];if (ff){int ci,d1,d2,us;for (us=u,i=uu-1;i>=0;--i)if (dep[fa[us][i]]>dep[ff]) us=fa[us][i];ans[fi[ff]]-=siz[us];for (ci=u,i=uu-1;i>=0;--i){if (dep[fa[ci][i]]<dep[ff]) continue;d1=dis(fi[u],fa[ci][i]);d2=dis(fi[ff],fa[ci][i]);if ((d1<d2)||(d1==d2&&fi[u]<fi[ff])) ci=fa[ci][i];}ans[fi[u]]+=siz[ci]-siz[u];ans[fi[ff]]+=siz[us]-siz[ci];}po1[u]=fi[u]=0; } int main(){int n,m,q,i,j,u,v;scanf("%d",&n);for (tot=0,i=1;i<n;++i){scanf("%d%d",&u,&v);add(u,v);}scanf("%d",&q);pre(1,0);for (i=1;i<=q;++i){scanf("%d",&m);v=tot=0;for (j=1;j<=m;++j){scanf("%d",&que[j]);hi[j]=que[j];vi[que[j]]=true;v=(!v ? que[j] : lca(v,que[j]));}sort(que+1,que+m+1,cmp);zh[top=1]=v;for (j=1;j<=m;++j){u=lca(zh[top],que[j]);while(1){if (dep[u]>=dep[zh[top-1]]){add1(u,zh[top--]);if (zh[top]!=u) zh[++top]=u;break;}add1(zh[top-1],zh[top]);--top;}if (que[j]!=zh[top]) zh[++top]=que[j];}for (;top>1;--top) add1(zh[top-1],zh[top]);if (v!=1) add1(1,v);v=1;up(v,0);down(v,0);for (j=1;j<=m;++j){printf("%d ",ans[hi[j]]);ans[hi[j]]=0;vi[hi[j]]=false;}printf("\n");} }View Code
?
樹型dp
CODEVS1380 沒有上司的舞會
思路:樹形dp。二維數組k[i][0](表示不取i結點),k[i][1](表示取i點)。用了美麗的next數組保存邊之間的關系。。。希望提高了效率。
#include<iostream>#include<cstdio>#include<cstring>using namespace std;int r[6001]={0},f[6001][2]={0},fa[6001]={0},next[6001]={0},point[6001]={0},ans;void work(int x){int i,p; p=point[x];if (p==0) {f[x][0]=0;f[x][1]=r[x];if (f[x][1]>ans) ans=f[x][1];return;}while (p!=0){work(p);f[x][0]=max(f[x][0],f[p][1]+f[x][0]);f[x][1]=max(f[x][1],f[p][0]+f[x][1]);p=next[p];}if (r[x]>0)f[x][1]=f[x][1]+r[x];if (f[x][0]>ans) ans=f[x][0];if (f[x][1]>ans) ans=f[x][1];}int main(){int n,i,j,l,k;cin>>n;ans=-2100000000;for (i=1;i<=n;++i)cin>>r[i];for (i=1;i<n;++i){cin>>l>>k;fa[l]=k;next[l]=point[k];point[k]=l;}cin>>l>>k;for (i=1;i<=n;++i)if (fa[i]==0){work(i);cout<<ans<<endl;break;}}View Code
?
?CODEVS1163訪問藝術館
題目描述?Description? ??皮爾是一個出了名的盜畫者,他經過數月的精心準備,打算到藝術館盜畫。藝術館的結構,每條走廊要么分叉為二條走廊,要么通向一個展覽室。皮爾知道每個展室里藏畫的數量,并且他精確地測量了通過每條走廊的時間,由于經驗老道,他拿下一副畫需要5秒的時間。你的任務是設計一個程序,計算在警察趕來之前(警察到達時皮爾回到了入口也算),他最多能偷到多少幅畫。
思路:不一定要取完一個畫室中的畫,取一幅畫要5s。先用遞歸建樹,保存一下父節點,兒子節點,到父親的距離,自己末端的畫數(有的變量可能用不到)。然后開始樹狀dp,f[i][j]表示第i個節點取j幅畫,然后對左右子樹進行選擇。一點點的算出來。
注意:每次循環就是從0-該子樹最多的畫數(用一個數組保存一下);work時,從0開始遞歸,因為input時把1的父節點默認為了0。
#include<cstdio> #include<iostream> #include<cstring> using namespace std; struct use{ int fa,d,va,l,r; }a[1000]; int s,n=0,su[500]={0},ans=0; long long f[500][10000]={0}; void input(int faa) { int x,y,t; cin>>x>>y; ++n;t=n; if (a[faa].l==0) a[faa].l=n; else a[faa].r=n; a[n].fa=faa; a[n].d=x*2; if (y==0) { input(t); input(t); } elsea[t].va=y; } void work(int i) { int j,k,l1,l2; if (a[i].l==0) { su[i]=a[i].va; for (j=1;j<=a[i].va;++j)f[i][j]=5*j; return;} work(a[i].l); if (i!=0)work(a[i].r); f[i][0]=0; for (k=1;k<=su[a[i].r];++k)f[i][k]=min(f[i][k],f[a[i].r][k]+a[a[i].r].d); for (j=1;j<=su[a[i].l];++j)f[i][j]=min(f[i][j],f[a[i].l][j]+a[a[i].l].d); for (j=1;j<=su[a[i].l];++j)for (k=1;k<=su[a[i].r];++k)f[i][j+k]=min(f[i][j+k],f[a[i].l][j]+a[a[i].l].d+a[a[i].r].d+f[a[i].r][k]); su[i]=su[a[i].l]+su[a[i].r]; } int main() { int i,j; cin>>s; memset(a,0,sizeof(a)); memset(f,127,sizeof(f));input(0);work(0);for (i=1;i<=su[0];++i)if (f[0][i]<=s&&i>ans) ans=i;cout<<ans<<endl; }View Code
?
?題目描述?Description
設一個n個節點的二叉樹tree的中序遍歷為(l,2,3,…,n),其中數字1,2,3,…,n為節點編號。每個節點都有一個分數(均為正整數),記第j個節點的分數為di,tree及它的每個子樹都有一個加分,任一棵子樹subtree(也包含tree本身)的加分計算方法如下:
subtree的左子樹的加分× subtree的右子樹的加分+subtree的根的分數
若某個子樹為主,規定其加分為1,葉子的加分就是葉節點本身的分數。不考慮它的空
子樹。
試求一棵符合中序遍歷為(1,2,3,…,n)且加分最高的二叉樹tree。要求輸出;
(1)tree的最高加分
(2)tree的前序遍歷
現在,請你幫助你的好朋友XZ設計一個程序,求得正確的答案。
思路: 比較簡單的樹形dp,每次都窮舉根節點,然后簡單的遞歸。不過要輸出路徑,可以用最短路時的思路,保存l、r為左右端點的根節點,然后遞歸輸出。還有就是要判斷是否該區間已有值,若有值就一定是最優值,不需要在窮舉了。
#include<iostream>#include<cstdio>using namespace std;long long f[50][50]={0};int a[50]={0},path[50][50]={0};void print(int l,int r){if (l>r) return;if (path[l][r]!=0){printf("%d ",path[l][r]);print(l,path[l][r]-1);print(path[l][r]+1,r);}}void work(int l,int r){int j;if (l>r){f[l][r]=1;path[l][r]=0;return;}if (f[l][r]!=0) return;if (l==r){f[l][r]=a[l];path[l][r]=l;return;}for (j=l;j<=r;++j){work(l,j-1);work(j+1,r);if (f[l][j-1]*f[j+1][r]+a[j]>f[l][r]){f[l][r]=f[l][j-1]*f[j+1][r]+a[j];path[l][r]=j;}}}int main(){int i,j,n;scanf("%d",&n);for (i=1;i<=n;++i)scanf("%d",&a[i]);work(1,n);printf("%d\n",f[1][n]);print(1,n);cout<<endl;}View Code
?
bzoj1017 魔獸地圖(!!!)
題目大意:有n中裝備,有的是基礎裝備、有的是高級的,高級的需要其他裝備來合成它,且合成關系是一棵樹。已知所有裝備的價值、基礎裝備的價格和數量上限、高級裝備的合成路線,求最大價值。
思路:樹上dp。可以設va[soni]表示soni合成i這個兒子所要的個數,vv[i]表示i這個點的花費,up[i]表示i這個點最多取幾個,fi[i][j][k]表示i這個點有j個是給父親合成用的花費為k的最大價值;gi[i][j]表示前i個兒子花費為j的最大價值,窮舉k個來合成父親,gi[i][j]=max(gi[i-1][ll-l]+fi[soni][k*va[soni]][l]);再回來更新fi[][][],fi[i][j][k]=max(gi[sizi][l]+(ll-l)*st[i])。
注意:1、dp下標要搞清楚;2、把數組清成相加不會炸int的可以用memset(fi,-0x3f3f3f3f,sizeof(fi))。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define inf 2100000000 using namespace std; int m,fi[55][105][2005],point[55]={0},en[10000]={0},next[10000]={0},va[10000]={0},ru[55]={0},st[55]={0},up[55]={0},vv[55]={0},tot=0,ans=0,gi[55][2005]; void add(int u,int v,int vaa){next[++tot]=point[u];point[u]=tot;en[tot]=v;va[tot]=vaa;++ru[v]; } void dp(int u){int i,j,k,v,siz=0,l;for (i=point[u];i;i=next[i]){v=en[i];dp(v);++siz;vv[u]+=va[i]*vv[v];}if (!siz){up[u]=min(up[u],m/vv[u]);for (i=0;i<=up[u];++i)for (j=i;j<=up[u];++j)ans=max(ans,fi[u][i][j*vv[u]]=(j-i)*st[u]);}else{for (up[u]=inf,i=point[u];i;i=next[i]) up[u]=min(up[u],up[en[i]]/va[i]);up[u]=min(up[u],m/vv[u]);memset(gi,-0x3f3f3f3f,sizeof(gi));gi[0][0]=0;for (j=up[u];j>=0;--j){int maxn=0;for (siz=0,i=point[u];i;i=next[i]){v=en[i];++siz;for (k=0;k<=m;++k)for (l=0;l<=k;++l)gi[siz][k]=max(gi[siz][k],gi[siz-1][k-l]+fi[v][va[i]*j][l]);}for (k=0;k<=j;++k)for (l=0;l<=m;++l){fi[u][k][l]=max(fi[u][k][l],gi[siz][l]+(j-k)*st[u]);ans=max(ans,fi[u][k][l]);}}} } int main(){int n,i,j,c,u,v;char ch;scanf("%d%d",&n,&m);for (i=1;i<=n;++i){scanf("%d%*c%c",&st[i],&ch);if (ch=='A'){scanf("%d",&c);for (j=1;j<=c;++j){scanf("%d%d",&u,&v);add(i,u,v);}}else scanf("%d%d",&vv[i],&up[i]);}memset(fi,-0x3f3f3f3f,sizeof(fi));for (i=1;i<=n;++i) if(!ru[i])dp(i);printf("%d\n",ans); }View Code
?
bzoj1217 消防局的設立||poj2152 消防站(!!!)
題目大意:給定一棵樹,在一些點上建消防站,使得任意一個點都能在di距離內找到一個消防站,求建滿足的消防站的代價wi和最小。
思路:bzoj上的題目沒有邊權同時di=2,所以可以樹上dp一下。但是對于poj上的題目就要復雜很多,看了論文之后有所理解。
我們可以設gi[i]表示i這個點的子樹都滿足要求(同時能滿足要求的消防站都在i這個點的子樹內),fi[i][j]表示i這個子樹內滿足要求(必須要選j作為消防站)的最優解。然后考慮dp轉移,gi[i]就是所有j是i子樹內的點的fi[i][j]最小值;fi[i][j]的轉移就是:如果dis[i][j]>di[i],fi[i][j]=inf;如果dis[i][j]<=di[i],那么:如果j是i,fi[i][j]=wi[i]+sigma(k是i的兒子)min(fi[k][j],gi[k]);如果j是i子樹中的,fi[i][j]=fi[ch][j](j在ch的子樹中,且ch是i的兒子)+sigma(k是i的兒子,且k!=ch)(fi[k][j],gi[k]);如果j在i的子樹外面,fi[i][j]=sigma(k是i的兒子)min(fi[k][j],gi[k])。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define maxm 2005 #define inf 2100000000 using namespace std; int point[maxm]={0},next[maxm]={0},en[maxm]={0},va[maxm]={0},fi[maxm][maxm]={0},gi[maxm]={0},dis[maxm][maxm]={0},wi[maxm]={0},di[maxm]={0},tot=0,pre[maxm]={0},las[maxm]={0},n; void add(int u,int v,int vv){next[++tot]=point[u];point[u]=tot;en[tot]=v;va[tot]=vv;next[++tot]=point[v];point[v]=tot;en[tot]=u;va[tot]=vv; } void dfs(int u,int fa,int kk){int i,j,v;pre[u]=++tot;for (i=point[u];i;i=next[i]){if ((v=en[i])==fa) continue;dis[kk][v]=dis[kk][u]+va[i];dfs(v,u,kk);}las[u]=tot; } void dp(int u,int fa){int i,j,v,ch,sum;for (i=point[u];i;i=next[i]){if ((v=en[i])==fa) continue;dp(v,u);}for (i=1;i<=n;++i){if (dis[i][u]>di[u]) fi[u][i]=inf;else{sum=0;for (j=point[u];j;j=next[j]){if ((v=en[j])==fa) continue;sum+=min(gi[v],fi[v][i]);}if (i==u){fi[u][i]=wi[i]+sum;gi[u]=min(gi[u],fi[u][i]);}else{if (pre[i]>=pre[u]&&las[i]<=las[u]){for (ch=point[u];ch;ch=next[ch]){if ((v=en[ch])==fa) continue;if (pre[i]>=pre[v]&&las[i]<=las[v]) break;}ch=en[ch];fi[u][i]=fi[ch][i]+sum-min(gi[ch],fi[ch][i]);gi[u]=min(gi[u],fi[u][i]);}else fi[u][i]=sum;}}} } int main(){int i,j,u,v,l,ans;scanf("%d",&n);for (i=1;i<=n;++i) scanf("%d",&wi[i]);for (i=1;i<=n;++i) scanf("%d",&di[i]);for (i=1;i<n;++i){scanf("%d%d%d",&u,&v,&l);add(u,v,l);}for (i=n;i>=1;--i){tot=0;dfs(i,0,i);}memset(gi,127,sizeof(gi));dp(1,0);printf("%d\n",gi[1]); }View Code
?
bzoj4011?落憶楓音
題目大意:給定一個有向無環圖,加上一條有向邊,求圖的生成樹的個數(要求以1為根,能到所有點)。
思路:如果無環,就是入度之積。加上一條邊后可能有環,所以要在所有答案里減去有環的那些,可以用總的方案*環的概率表示這個環存在的個數,這個概率可以用dp來求(拓撲序進行),從這條形成環的邊的終點處才有dp值,因為要除,所以要逆元,拓撲的時候不能考慮那條環邊,但是答案里要考慮。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define maxm 200005 #define LL long long #define p 1000000007LL using namespace std; int point[maxm]={0},next[maxm]={0},en[maxm]={0},tot=0,que[maxm]={0}; LL rd[maxm]={0LL},fi[maxm]={0LL},ci[maxm],ni[maxm]={0},ans; void add(int u,int v){next[++tot]=point[u];point[u]=tot;en[tot]=v;++rd[v];} LL mi(LL x,LL y){if (y==0) return 1LL;if (y==1) return x%p;LL mm=mi(x,y/2);if (y%2) return mm*mm%p*x%p;else return mm*mm%p; } void dfs(int n,int ui,int vi){int u,v,i,j,head=0,tail=0;for (i=1;i<=n;++i){ni[i]=mi(rd[i],p-2);ci[i]=rd[i]-(i==vi);}que[++tail]=1;while(head!=tail){u=que[++head];if (u==vi) fi[u]=ni[u];for (i=point[u];i;i=next[i]){fi[v=en[i]]=(fi[v]+fi[u]*ni[v]%p)%p;if (!(--ci[v])) que[++tail]=v;}}ans=((ans-ans*fi[ui]%p)%p+p)%p; } int main(){int i,j,n,m,u,v,ui,vi;ans=1LL;scanf("%d%d%d%d",&n,&m,&ui,&vi);for (i=1;i<=m;++i){scanf("%d%d",&u,&v);add(u,v);}for (i=2;i<=n;++i) ans=ans*(rd[i]+=(i==vi))%p;dfs(n,ui,vi);printf("%I64d\n",ans); }View Code
?
bzoj1065 奧運物流(!!!)
題目大意:每個點有一個父親、常數ci、k,一個點i的權值vi=c+k*sigma(j是i的兒子)vj,可以至多改變m個點的后繼,求根節點1的最大權值。
思路:每個點的貢獻是ci*k^(dep-1)*(1+k^len+k^2len...)=ci*k^(dep-1)*(1-k^nlen)/(1-k^len),len表示環的長度,因為k<1,所以可以看作=ci*k^(dep-1)/(1-k^len),所以一個點的dep越小,貢獻越大,所以修改一定是把一個點的父親換為1,樹型dp。先枚舉環的長度就可以確定樹的形態了,然后dfs一遍,更新答案。
注意:做背包的部分的時候,因為新兒子可能選0,所以不能直接用dp的數組,而要單開一個變量保存最優值,然后賦過去。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define N 65 #define LD double #define inf 2100000000 using namespace std; int point[N]={0},next[N],en[N],si[N],tot=0,dep[N],cl,m,gi[N]={0}; LD ci[N],fi[N][N][N]={0.},mi[N]; bool cir[N]={false}; void add(int u,int v){next[++tot]=point[u];point[u]=tot;en[tot]=v;} void dfs(int u,int de){int i;dep[u]=de;for (i=point[u];i;i=next[i]) dfs(en[i],de+1); } void dp(int u,int len,int de){int i,j,a,b,v;for (i=point[u];i;i=next[i]) dp(en[i],len,de+1);for (i=1;i<=de;++i){if (i==1&&de>1) continue;if (i!=2&&gi[u]==len) continue;if (gi[u]&&gi[u]<len&&u!=1&&i!=(len-gi[u]+2)) continue;fi[u][i][0]=ci[u]*mi[i-1]/(1.-mi[len]);for (j=point[u];j;j=next[j]){v=en[j];for (a=m;a>=0;--a){if (!a) fi[u][i][a]+=fi[v][i+1][a];else{LD mx=-inf;for (b=0;b<=a;++b)mx=max(mx,fi[u][i][a-b]+max(fi[v][i+1][b],b ? fi[v][2][b-1] : -inf));fi[u][i][a]=mx;}}}} } int main(){int n,i,j,a,b,c;LD ans=0.,k;scanf("%d%d%lf",&n,&m,&k);for (i=1;i<=n;++i){scanf("%d",&si[i]);if (i>1) add(si[i],i);}for (i=1;i<=n;++i) scanf("%lf",&ci[i]);for (mi[0]=1.,i=1;i<N;++i) mi[i]=mi[i-1]*k;dfs(1,1);cir[1]=true;gi[1]=1;for (j=1,i=si[1];i!=1;i=si[i]){cir[i]=true;gi[i]=++j;}for (i=cl=dep[si[1]];i>=2;--i){for (a=1;a<=n;++a)for (b=0;b<=n;++b)for (c=0;c<=m;++c) fi[a][b][c]=-inf;dp(1,i,1);ans=max(ans,fi[1][1][m]);}printf("%.2f\n",ans); }View Code
?
bzoj1063 道路設計
題目大意:給定一個森林結構,可以選擇一些不相交的鏈改建為鐵路,定義一個點的不舒服值是從這個點到根節點1的路徑上經過的未改建的路數,求不舒服值最大的那個點最小值和方案數。
思路:如果這不是一棵樹,就輸出-1-1;樹的情況下可以dp,fi[i][j][k]表示i這個點以及子樹中的點到i的不舒服值最大為j,i和兒子連接狀況為k的方案數(0<=k<=2,分別表示不和兒子相連,和一個和兩個兒子相連),對于k=2的情況,用之前的k=1和新兒子更新;k=1的用k=0和新兒子更新;k=0的用兒子的1、2、3更新。注意方案數是乘法原理,所以考慮新兒子的時候,如果新兒子不與父親連接的話,還要給原來的情況中*表示新兒子不與父親相連的情況。
注意:因為有%q,所以還要處理一下%q==0的情況,可以%的時候(x-1)%q+1,這樣存在方案的就不是0了。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define N 100005 #define up 20 #define LL long long using namespace std; int point[N]={0},next[N<<1],en[N<<1],tot=0,cnt=0; LL q,fi[N][up][3]={0}; void add(int u,int v){next[++tot]=point[u];point[u]=tot;en[tot]=v;next[++tot]=point[v];point[v]=tot;en[tot]=u;} void dp(int u,int fa){int i,j,v;LL a,b;++cnt;for (i=point[u];i;i=next[i]){if ((v=en[i])==fa) continue;dp(v,u);for (j=0;j<up;++j){a=fi[v][j][0]+fi[v][j][1];b=(j ? fi[v][j-1][0]+fi[v][j-1][1]+fi[v][j-1][2] : 0LL);fi[u][j][2]=(fi[u][j][2]*b+fi[u][j][1]*a-1)%q+1;fi[u][j][1]=(fi[u][j][1]*b+fi[u][j][0]*a-1)%q+1;fi[u][j][0]=(fi[u][j][0]*b-1)%q+1;}} } int main(){int n,m,i,j,u,v;scanf("%d%d%I64d",&n,&m,&q);for (i=1;i<=m;++i){scanf("%d%d",&u,&v);add(u,v);}for (i=1;i<=n;++i)for (j=0;j<up;++j) fi[i][j][0]=1LL;dp(1,0);if (cnt<n) printf("-1\n-1\n");else{for (j=0;j<up;++j)if (fi[1][j][0]+fi[1][j][1]+fi[1][j][2]) break;printf("%d\n%I64d\n",j,(fi[1][j][0]+fi[1][j][1]+fi[1][j][2])%q);} }View Code
?
bzoj4013 實驗比較
題目大意:已知一些圖片間的關系,形如a<b或者a=b,每個b是不同的,合法序列是包含n個圖片的,形如a1</=a2</=a3...,求合法序列的個數。
思路:因為每個b不同,所以是一個森林或者環。如果有環或者兩個數既=有<都是0。設fi[i][j]表示i這個點后面序列長為j的方案數,考慮合并兩個兒子u、v,枚舉u的長度a、v的長度b、合并后的長度k(max(a,b)<=k<=a+b),相當于向k個盒子中放入a個白球和b個黑球,每個盒子每種顏色只能放一個,方案是c(k,a)*c(a,b-k+a),相當于先選a個放白球、剩下的放黑球、再從a個放白球的中間選出b-k+a個放黑球,對于樹上的點,要放到j+1的位置。
注意:判斷環的時候非常多情況。
#include<iostream> #include<cstring> #include<cstdio> #include<algorithm> #define N 205 #define LL long long #define p 1000000007LL using namespace std; int fa[N],point[N]={0},next[N],en[N],tot=0,zh[N]={0},scc[N]={0},scnt=0,pre[N],low[N],tt=0,po1[N]={0},ne1[N],en1[N],cnt=0,siz[N]={0},du[N]={0},map[N][N]={0}; LL fac[N],inv[N],fi[N][N]={0LL},c[N][N],ans=1LL,gi[N]; bool vi[N]={false},mp[N][N]={false}; void add(int u,int v){next[++tot]=point[u];point[u]=tot;en[tot]=v;} void add1(int u,int v){if (mp[u][v]) return;mp[u][v]=true;ne1[++tot]=po1[u];po1[u]=tot;en1[tot]=v;++du[v];} char in(){char ch=getchar();while(ch!='='&&ch!='<') ch=getchar();return ch;} void tarjan(int u){int i,v;zh[++zh[0]]=u;pre[u]=low[u]=++tt;for (i=point[u];i;i=next[i]){v=en[i];if (!pre[v]){tarjan(v);low[u]=min(low[u],low[v]);}else if (!scc[v]) low[u]=min(low[u],pre[v]);}if (pre[u]==low[u]){++scnt;while(zh[0]){scc[v=zh[zh[0]]]=scnt;--zh[0];if (v==u) break;}} } void dfs2(int u,int anc){int i,v;if (vi[scc[anc]]) return;for (i=point[u];i;i=next[i]){v=en[i];if (pre[v]){if (pre[u]+map[u][v]-1-pre[v]>=1) vi[scc[anc]]=true;continue;}else{pre[v]=pre[u]+map[u][v]-1;dfs2(v,anc);}} } void dfs(int u){int i,k,a,b,v;for (i=po1[u];i;i=ne1[i]) dfs(en1[i]);for (i=po1[u];i;i=ne1[i]){v=en1[i];if (i==po1[u]){siz[u]+=siz[v];for (k=1;k<=siz[u];++k) fi[u][k]=fi[v][k];}else{memset(gi,0,sizeof(gi));for (a=1;a<=siz[u];++a)for (b=1;b<=siz[v];++b)for (k=max(a,b);k<=a+b;++k)gi[k]=(gi[k]+fi[u][a]*fi[v][b]%p*c[k][a]%p*c[a][b-k+a]%p)%p;siz[u]+=siz[v];for (k=1;k<=siz[u];++k) fi[u][k]=gi[k];}}if (!po1[u]) fi[u][1]=1LL;else for (i=siz[u]+1;i;--i) fi[u][i]=fi[u][i-1];++siz[u];} int main(){int i,j,n,m,u,v,a,b,k;char ch;scanf("%d%d",&n,&m);for (i=0;i<N;++i)for (c[i][0]=1LL,j=1;j<=i;++j) c[i][j]=(c[i-1][j]+c[i-1][j-1])%p;for (i=1;i<=m;++i){scanf("%d",&u);ch=in();scanf("%d",&v);add(u,v);if (ch=='='){add(v,u);if (map[v][u]>1||map[u][v]>1){printf("0\n");return 0;}map[u][v]=map[v][u]=1;}else{if (map[u][v]==1){printf("0\n");return 0;}map[u][v]=2;}}for (i=1;i<=n;++i) if (!pre[i]) tarjan(i);for (i=1;i<=n;++i){memset(pre,0,sizeof(pre));pre[i]=1;dfs2(i,i);if (vi[scc[i]]){printf("0\n");return 0;}}for (tot=0,i=1;i<=n;++i)for (j=point[i];j;j=next[j])if (scc[i]!=scc[en[j]]) add1(scc[i],scc[en[j]]);for (j=0,i=1;i<=scnt;++i){if (du[i]) continue;dfs(i);if (!j){siz[0]+=siz[i];for (k=1;k<=siz[0];++k) fi[0][k]=fi[i][k];}else{memset(gi,0,sizeof(gi));for (a=1;a<=siz[0];++a)for (b=1;b<=siz[i];++b)for (k=max(a,b);k<=a+b;++k)gi[k]=(gi[k]+fi[0][a]*fi[i][b]%p*c[k][a]%p*c[a][b-k+a]%p)%p;siz[0]+=siz[i];for (k=1;k<=siz[0];++k) fi[0][k]=gi[k];}j=i;}for (ans=0LL,i=1;i<=n;++i) ans=(ans+fi[0][i])%p;printf("%I64d\n",ans); }View Code
?
bzoj4446 小凸玩密室
題目大意:一棵完全二叉樹,有點權和邊權,每個點有一個燈,第一燈不花代價,第二個燈v的代價是dis(la,v)*ai[v],每次點的燈必須和之前的聯通,問點亮所有燈的最小代價。
思路:fi[i][j]表示i的子樹點完,最后點的是j,必須先點i的最小代價;gi[i][j]表示i的子樹點完,最后點的是j,可以先點其他的最小代價。因為是完全二叉樹,所以狀態是nlogn的。枚舉左右兒子用最優值更新,也是nlogn的。
注意:因為二叉樹可能不滿,可以把最后一層補滿。這些點的邊權值0,但點權不能為0(可能出現本來停在這個子樹的其他點,但因為點權是0,所以又過來了的情況,再向上更新的時候有問題),賦成一個相對較大的數(也不能乘爆LL)。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define N 300005 #define up 20 #define LL long long #define inf 0x7fffffffffffffffLL using namespace std; int n,li[N],ri[N]; LL ai[N]={0LL},bi[N]={0LL},dep[N]={0LL},fi[up][N],gi[up][N]; void dp(int u,int dd){int i,v,ls,rs;LL lf,lg,rf,rg;dep[u]=dep[u>>1]+bi[u-1];lf=lg=rf=rg=inf/3LL;ls=((u<<1)<=n ? (u<<1) : 0);rs=((u<<1|1)<=n ? (u<<1|1) : 0);if (ls){dp(ls,dd+1);dp(rs,dd+1);for (i=li[ls];i<=ri[ls];++i){lf=min(lf,fi[dd+1][i]+(dep[i]-dep[u])*ai[rs]);lg=min(lg,gi[dd+1][i]+(dep[i]-dep[u])*ai[u]);}lf+=(dep[ls]-dep[u])*ai[ls]; for (i=li[rs];i<=ri[rs];++i){rf=min(rf,fi[dd+1][i]+(dep[i]-dep[u])*ai[ls]);rg=min(rg,gi[dd+1][i]+(dep[i]-dep[u])*ai[u]);}rf+=(dep[rs]-dep[u])*ai[rs];}else fi[dd][u]=gi[dd][u]=lf=lg=rf=rg=0LL;if (ls){for (i=li[ls];i<=ri[ls];++i){fi[dd][i]=min(fi[dd][i],fi[dd+1][i]+(dep[ls]-dep[u])*ai[ls]+rf);gi[dd][i]=min(gi[dd][i],fi[dd+1][i]+(dep[ls]-dep[u])*ai[ls]+rg);}for (i=li[rs];i<=ri[rs];++i){fi[dd][i]=min(fi[dd][i],fi[dd+1][i]+(dep[rs]-dep[u])*ai[rs]+lf);gi[dd][i]=min(gi[dd][i],fi[dd+1][i]+(dep[rs]-dep[u])*ai[rs]+lg);}}for (i=li[u];i<=ri[u];++i) gi[dd][i]=min(gi[dd][i],fi[dd][i]); } int main(){int i,j;LL ans;scanf("%d",&n);memset(fi,127/3,sizeof(fi));memset(gi,127/3,sizeof(gi));for (i=1;i<N;++i) ai[i]=inf/1000000000LL;for (i=1;i<=n;++i) scanf("%I64d",&ai[i]);for (i=1;i<n;++i) scanf("%I64d",&bi[i]);for (i=1;i<=n;i<<=1);n=i-1;for (i=1;i<=n;++i){for (li[i]=ri[i]=i;(li[i]<<1)<=n;li[i]=li[i]<<1,ri[i]=ri[i]<<1|1);ri[i]=min(ri[i],n);}dp(1,1);ans=inf;for (i=1;i<=n;++i) ans=min(ans,min(fi[1][i],gi[1][i]));printf("%I64d\n",ans); }View Code
?
HAOI2015樹上染色
題目大意:給樹上n個點染k個黑色,求黑點之間、白點之間距離和的最大值。
思路:感覺是樹形dp,但是覺得轉移的時候很難受。后來聽到fye大神的背包,就開始想背包方向想,發現多重背包可以解決。我們dfs一個孩子之后就可以進行背包了。但是賦初值的時候,要給f[u][0]和f[u][1]都賦為0,因為對于u這個根節點,我們可以涂兩種顏色,初始值中的f[u][0]表示把它涂成白色,而f[u][1]表示涂成黑色,其他都是-∞就可以了。
#include<iostream> #include<cstdio> #include<algorithm> #include<cstring> #define maxnode 2001 using namespace std; int point[maxnode]={0},next[maxnode*2]={0},en[maxnode*2]={0},tot=0,siz[maxnode]={0},n,kk; long long va[maxnode*2]={0},f[maxnode][maxnode]={0}; bool visit[maxnode]={false}; void add(int u,int v,long long st) {++tot;next[tot]=point[u];point[u]=tot;en[tot]=v;va[tot]=st;++tot;next[tot]=point[v];point[v]=tot;en[tot]=u;va[tot]=st; } void dfs(int u) {int i,j,k,t;long long sum;siz[u]=1;visit[u]=true;f[u][0]=f[u][1]=0;for (i=point[u];i;i=next[i])if (!visit[j=en[i]]){dfs(j);siz[u]+=siz[j];for (k=siz[u];k>=0;--k)for (t=0;t<=siz[j]&&t<=k;++t){sum=f[u][k-t]+f[j][t]+va[i]*(long long)(t*(kk-t))+va[i]*(long long)((siz[j]-t)*(n-kk-(siz[j]-t)));f[u][k]=max(f[u][k],sum);}} } int main() {int i,j,u,v;long long st;scanf("%d%d",&n,&kk);for (i=1;i<n;++i){scanf("%d%d%I64d",&u,&v,&st);add(u,v,st);}for (i=0;i<maxnode;++i)for (j=0;j<maxnode;++j)f[i][j]=-999999999999;dfs(1);printf("%I64d\n",f[1][kk]); }View Code
?
bzoj3677 連珠線
題目大意:每次有兩種操作:(1)把一個珠子和一串珠子中的連起來紅線;(2)把一個珠子放到紅線中間,并用藍線把珠子和兩端連起來。給出一個最終的形狀,權值和是藍線權值和,問最大權值和。
思路:藍線都是兩兩相連。fi[i][j]表示i的時候5中狀態的連線:0、i有0個不配對的藍線;1、i有1個不配對的藍線;2、i有兩個配對的藍線;3、i有0個不配對的籃線且子樹中有兩個配對的藍線;4、i有1個不配對的藍線且子樹中有兩個配對的藍線。互相更新。
注意:有一種不符合的情況就是兩對藍線通過中間的點連起來,很多情況都不能更新。(!!!)
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define N 200005 #define LL long long using namespace std; int point[N]={0},next[N<<1],en[N<<1],tot=0; LL va[N<<1],fi[N][5]; 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;} void add(int u,int v,LL vv){next[++tot]=point[u];point[u]=tot;en[tot]=v;va[tot]=vv;next[++tot]=point[v];point[v]=tot;en[tot]=u;va[tot]=vv;} void dp(int u,int ff){int i,v;LL f0,f1,f2,f3,f4,v0,v1,v2,v3,v4;fi[u][0]=0LL;for (i=point[u];i;i=next[i]){if ((v=en[i])==ff) continue;dp(v,u);v0=fi[v][0];v1=fi[v][1];v2=fi[v][2];v3=fi[v][3];v4=fi[v][4];f0=fi[u][0];f1=fi[u][1];f2=fi[u][2];f3=fi[u][3];f4=fi[u][4];fi[u][0]=max(f0,max(f0+va[i]+v1,f0+v0));fi[u][1]=max(f1,max(f0+v0+va[i],f1+max(v0,va[i]+v1)));fi[u][2]=max(f2,max(f1+va[i]+max(v0,max(v2,v3)),max(f2+max(va[i]+v1,v0),f4+va[i]+v0)));fi[u][3]=max(f3,max(f3+max(v0,va[i]+v1),max(f3+v0,f0+max(v2,max(v3,v4+va[i])))));fi[u][4]=max(f4,max(f0+max(v2,v3)+va[i],f4+max(v0,va[i]+v1)));} } int main(){int n,i,u,v,vv;n=in();memset(fi,-60,sizeof(fi));for (i=1;i<n;++i){u=in();v=in();vv=in();add(u,v,(LL)vv);}dp(1,0);printf("%I64d\n",max(fi[1][0],max(fi[1][2],fi[1][3]))); }View Code
?
bzoj4557 偵查守衛
題目大意:每個守衛可以覆蓋距離d以內的點,問覆蓋樹上m個給定點的最小代價。
思路:fi[i][j]表示i的子樹都覆蓋,至少還能覆蓋j的距離的最小代價;gi[i][j]表示i的子樹中,至多距離j內的沒有覆蓋的最小代價。轉移的時候用sm[i]表示子樹中之多j沒被覆蓋的和,更新的時候考慮這個點放活著不放,放的話fi[u][d]=sm[d]+wi[u];用某個孩子的fi[i][j]能覆蓋其他孩子更新fi[u][j-1],gi[u][j]的更新可以直接用sm[j],對于不用覆蓋的fi[u][0]也可以用sm[0]更新。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define N 500005 #define LL long long #define M 21 #define inf 1000000000000LL using namespace std; int point[N],next[N<<1],en[N<<1],tot=0,d,wi[N],fl[N]={0}; LL fi[N][M],gi[N][M],sm[M]; 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;} void add(int u,int v){next[++tot]=point[u];point[u]=tot;en[tot]=v;next[++tot]=point[v];point[v]=tot;en[tot]=u;} void dp(int u,int fa){int i,j,v,sz=0;for (i=0;i<=d;++i) fi[u][i]=gi[u][i]=inf;for (i=point[u];i;i=next[i]){if ((v=en[i])==fa) continue;dp(v,u);++sz;}if (!sz){for (i=0;i<=d;++i){fi[u][i]=wi[u];gi[u][i]=0;}if (!fl[u]) fi[u][0]=0;gi[u][0]=inf;return;}memset(sm,0,sizeof(sm));for (i=point[u];i;i=next[i]){if ((v=en[i])==fa) continue;for (j=0;j<=d;++j) sm[j]=min(inf*2LL,sm[j]+min(fi[v][0],gi[v][j]));}fi[u][d]=min(fi[u][d],wi[u]+sm[d]);for (i=point[u];i;i=next[i]){if ((v=en[i])==fa) continue;for (j=0;j<d;++j) fi[u][j]=min(fi[u][j],sm[j]-min(fi[v][0],gi[v][j])+fi[v][j+1]);}for (i=1;i<=d;++i) gi[u][i]=min(gi[u][i],sm[i-1]);if (!fl[u]) fi[u][0]=min(fi[u][0],sm[0]);for (i=d-1;i>=0;--i) fi[u][i]=min(fi[u][i],fi[u][i+1]);for (i=1;i<=d;++i) gi[u][i]=min(gi[u][i],gi[u][i-1]); } int main(){int n,m,i,u,v;n=in();d=in();for (i=1;i<=n;++i) wi[i]=(LL)in();m=in();for (i=1;i<=m;++i) fl[in()]=1;for (i=1;i<n;++i){u=in();v=in();add(u,v);}dp(1,0);printf("%I64d\n",fi[1][0]); }View Code
?
bzoj4593 聚變反應爐
題目大意:給定一棵樹,每個點有一個激發能量di,激發之后可以向聯通的點傳送能量ci,問激發所有點的最小能量和。(兩檔數據:1)n<=100000,ci<=1;2)n<=2000,ci<=5)
思路:fi[i]表示i先炸、父親后炸;gi[i]表示i后炸、父親先炸。第一檔的數據可以直接貪心,按fi[i]-gi[i](fi一定>=gi)排序,更新fi、gi;第二檔的數據用背包。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define N 100005 #define M 200005 #define inf 1000000000 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 v,fg;bool operator<(const use&x)const{return fg<x.fg;} }uu[N]; int point[N]={0},next[M],en[M],ci[N]={0},di[N],tot=0,fi[N],gi[N],cc[N]; void add(int u,int v){next[++tot]=point[u];point[u]=tot;en[tot]=v;next[++tot]=point[v];point[v]=tot;en[tot]=u; } void dp(int u,int fa){int i,j,v,sz=0;fi[u]=gi[u]=inf;for (i=point[u];i;i=next[i]){if ((v=en[i])==fa) continue;dp(v,u);sz+=ci[v];}cc[0]=0;for (i=1;i<=sz;++i) cc[i]=inf;sz=0;for (i=point[u];i;i=next[i]){if ((v=en[i])==fa) continue;sz+=ci[v];for (j=sz;j>=0;--j)cc[j]=min(cc[j]+gi[v],cc[max(0,j-ci[v])]+fi[v]);}for (i=sz;i>=0;--i)fi[u]=min(fi[u],cc[i]+(i>=di[u] ? 0 : di[u]-i));int up=max(0,di[u]-ci[fa]);for (i=sz;i>=0;--i)gi[u]=min(gi[u],cc[i]+(i>=up ? 0 : up-i)); } void dp2(int u,int fa){int i,v,sz=0,sm=0;fi[u]=gi[u]=inf;for (i=point[u];i;i=next[i]){if ((v=en[i])==fa) continue;dp2(v,u);}for (i=point[u];i;i=next[i]){if ((v=en[i])==fa) continue;if (ci[v]) uu[++sz]=(use){v,fi[v]-gi[v]};sm+=gi[v];}sort(uu+1,uu+sz+1);uu[0].fg=0;for (i=0;i<=sz;++i){if (i) uu[i].fg+=uu[i-1].fg;fi[u]=min(fi[u],sm+uu[i].fg+max(0,di[u]-i));gi[u]=min(gi[u],sm+uu[i].fg+max(0,di[u]-ci[fa]-i));} } int main(){int n,i,u,v;n=in();for (i=1;i<=n;++i) di[i]=in();for (i=1;i<=n;++i) ci[i]=in();for (i=1;i<n;++i){u=in();v=in();add(u,v);}if (n<=2000) dp(1,0);else dp2(1,0);printf("%d\n",fi[1]); }View Code
?
bzoj1495 網絡收費(!!!)
題目大意:給出一棵n+1層的滿二叉樹,葉子節點有種類A/B,葉子節點(i,j)之間有代價:在lca處,如果子樹中A多于B,ij均為A代價0,ij一個為B代價為fij,ij均為B代價為2*fij;B多于A的時候類似。每個葉子節點修改種類的代價為ci,問最小的修改代價和代價之和。
思路:代價的計算方式相當于A多的時候B+代價,B多的時候A+代價。fi[i][j][k]表示i這個點的子樹中,祖先AB關系為j(二進制,為0表示A<B,為1表示A>=B),子樹中有k個B的最小代價。在葉子節點的地方計算出所有的代價,祖先的地方合并不同子樹的代價。j和k在一個點的地方是2^(n+1)的,這個可以同時算時間和空間復雜度,可以合并j和k為一維,對于每個點,兒子的個數是平均O(n)的,復雜度是O(2n*2^(2n+2))。
注意:分析時空復雜度的時候注意滿二叉樹的一些特殊性質,很多時候的復雜度都會優很多。
#include<iostream> #include<cstring> #include<cstdio> #include<algorithm> #define N 1050 #define M 2050 #define up 11 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 n,ai[M],bi[M],bz=0,ci[M],di[M],fi[M][M],hi[M][up],gi[M][M],inf; int idx(int dep,int j,int k){return (j<<(n-dep+2))|k;} void add(int &x,int y){if (x>y) x=y;} void dp(int u,int dep){int i,j,k,cc;if (dep==n+1){for (i=0;i<=1;++i)for (k=0;k<(1<<n);++k){cc=(i!=ai[u])*ci[u];for (j=1;j<=n;++j)if (((k>>(j-1))&1)==i) cc+=hi[u][j];gi[u][idx(dep,k,i)]=cc;}return;}int li,ri,sz,rr,ld,rd,ud,v;dp(u<<1,dep+1);dp(u<<1|1,dep+1);sz=1<<(n-dep+1);for (i=0;i<=1;++i){if (!i){li=1<<(n-dep)|1;ri=sz;}else{li=0;ri=1<<(n-dep);}for (j=0;j<(1<<(dep-1));++j)for (k=li;k<=ri;++k){rr=min(k,sz>>1);ud=idx(dep,j,k);for (v=max(0,k-(sz>>1));v<=rr;++v){ld=idx(dep+1,j<<1|i,v);rd=idx(dep+1,j<<1|i,k-v);if (gi[u<<1][ld]<inf&&gi[u<<1|1][rd]<inf)add(gi[u][ud],gi[u<<1][ld]+gi[u<<1|1][rd]);}}} } int main(){int i,j,u,ans;n=in();for (i=1;i<=(1<<n);++i){bi[i]=i+(1<<n)-1;ai[bi[i]]=in();}for (i=1;i<=(1<<n);++i) ci[bi[i]]=in();for (i=1;i<(1<<n);++i)for (j=i+1;j<=(1<<n);++j)fi[bi[i]][bi[j]]=fi[bi[j]][bi[i]]=in();memset(hi,0,sizeof(hi));for (i=1;i<=(1<<n);++i){for (j=(1<<(n+1))-1;j>=(1<<n);--j) di[j]=fi[j][bi[i]];for (;j;--j) di[j]=di[j<<1]+di[j<<1|1];for (j=1,u=bi[i];j<=n;++j,u>>=1) hi[bi[i]][j]=di[u^1];}memset(gi,127,sizeof(gi));inf=gi[0][0];dp(1,1);for (ans=inf,i=0;i<=(1<<n);++i)add(ans,gi[1][idx(1,0,i)]);printf("%d\n",ans); }View Code
?
bzoj1304 葉子的染色
題目大意:給出m個點的一棵樹,其中1~n是葉子,葉子有0/1的顏色,選一個非葉子的點為根,每個葉子到根路徑上離葉子最近的有顏色的點的顏色為葉子的顏色,問最小染色數。
思路:暴力的方法是枚舉根,然后樹形dp,fi[i][0/1]表示i這個點染0/1滿足葉子的所有條件的最小染色數,fi[u][j]=sigma(min(fi[v][j]-1,fi[v][j^1]))+1。然后有一些性質:1)一條邊的兩邊的顏色肯定不同;2)同一條邊的兩端點為根的答案一樣,所以可以制作一遍dp就可以了(!)。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define N 10005 #define M 20005 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 ci[N],fi[N][2],point[N]={0},next[M],en[M],tot=0,n,m; void add(int u,int v){next[++tot]=point[u];point[u]=tot;en[tot]=v;next[++tot]=point[v];point[v]=tot;en[tot]=u; } void dp(int u,int ff){if (u<=n){fi[u][ci[u]]=1;fi[u][ci[u]^1]=m;return;}int i,v;fi[u][0]=fi[u][1]=1;for (i=point[u];i;i=next[i]){if ((v=en[i])==ff) continue;dp(v,u);fi[u][0]+=min(fi[v][0]-1,fi[v][1]);fi[u][1]+=min(fi[v][1]-1,fi[v][0]);} } int main(){int i,u,v;m=in();n=in();for (i=1;i<=n;++i) ci[i]=in();for (i=1;i<m;++i){u=in();v=in();add(u,v);}dp(n+1,0);printf("%d\n",min(fi[n+1][0],fi[n+1][1])); }View Code
?
括號序列表示法(!!!)
bzoj1095 捉迷藏
題目大意:給定一棵樹,支持:1)開一個房間的燈;2)查詢樹上最遠的沒開燈的兩點的距離。
思路:動態樹分治的模板題,但寫起來非常麻煩,所以學習了括號序列表示的方法。把一棵樹按dfs序表上左右括號和點的編號,可以發現,兩點間去掉匹配的括號形成的)))((形式可以用數對(a,b)表示,而a+b就是兩點的距離,所以可以用線段樹維護這個序列。需要維護的量有:1)一個區間左面開始連續的(b-a)和(b+a)最大值(且這部分之后是一個關燈的房間),一個區間右面開始的(a+b)和(a-b)的最大值(且這部分之前是一個關燈的房間);2)一個區間去掉匹配括號之后的a和b。考慮兩個區間(a,b)(c,d)的合并,化式子可以發現這四個量可以更新。注意初始化的時候:區間的答案是-inf,括號和亮燈房間的四個最大值都是-inf,關燈房間的四個最大值是0,相應括號的a、b是1/0。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define N 300005 #define inf 1000000000 using namespace std; int point[N]={0},next[N],en[N],tot=0,tt=0,kd[N],nw[N],pos[N]; struct use{int ls,ld,rs,rd,lc,rc,ans;void init(int x){ans=-inf;lc=rc=0;if (kd[x]>0&&nw[kd[x]]) ls=ld=rs=rd=0;else ls=ld=rs=rd=-inf;if (kd[x]==-1) rc=1;if (kd[x]==-2) lc=1;} }tr[N<<2]; char in(){char ch=getchar();while(ch<'A'||ch>'Z') ch=getchar();return ch;} void add(int u,int v){next[++tot]=point[u];point[u]=tot;en[tot]=v;next[++tot]=point[v];point[v]=tot;en[tot]=u;} void dfs(int u,int f){int i,v;kd[++tt]=-1;kd[pos[u]=++tt]=u;for (i=point[u];i;i=next[i]){if ((v=en[i])==f) continue;dfs(v,u);}kd[++tt]=-2;} use updata(use x,use y){use c;c.ans=max(x.ans,max(y.ans,max(x.rs+y.ld,x.rd+y.ls)));c.rs=max(y.rs,max(x.rs-y.lc+y.rc,x.rd+y.lc+y.rc));c.rd=max(y.rd,x.rd+y.lc-y.rc);c.ld=max(x.ld,y.ld-x.lc+x.rc);c.ls=max(x.ls,max(y.ls+x.lc-x.rc,y.ld+x.lc+x.rc));if (x.rc>y.lc){c.lc=x.lc;c.rc=y.rc+x.rc-y.lc;}else {c.lc=x.lc+y.lc-x.rc;c.rc=y.rc;}return c;} void build(int i,int l,int r){if (l==r){tr[i].init(l);return;}int mid=(l+r)>>1;build(i<<1,l,mid);build(i<<1|1,mid+1,r);tr[i]=updata(tr[i<<1],tr[i<<1|1]);} void tch(int i,int l,int r,int x){if (l==r){tr[i].init(l);return;}int mid=(l+r)>>1;if (x<=mid) tch(i<<1,l,mid,x);else tch(i<<1|1,mid+1,r,x);tr[i]=updata(tr[i<<1],tr[i<<1|1]);} int main(){int i,j,n,m,u,v,nt=0;char ch;scanf("%d",&n);for (i=1;i<n;++i){scanf("%d%d",&u,&v);add(u,v);}for (i=1;i<=n;++i) nw[i]=1;dfs(1,0);build(1,1,tt);scanf("%d",&m);nt=n;for (i=1;i<=m;++i){ch=in();if (ch=='G') printf("%d\n",(nt<=1 ? nt-1 : tr[1].ans));else{scanf("%d",&j);nt+=(!nw[j] ? 1 : -1);nw[j]^=1;tch(1,1,tt,pos[j]);}} }View Code
?
dfs序和bfs序
bzoj3244 樹的計數(!!!)
題目大意:給出dfs序和bfs序,求所有滿足兩種序的樹高的期望。(兩種順序中兒子的先后順序不變)
思路:為了方便統計,先按bfs的順序重新標號,記錄dfs序ai和dfs序中i的位置pos。對于bfs序分段,分的段數就是樹高。
?有以下條件:1)1單獨一個段;2)一段[l,r]中,pos[l]<pos[l+1]<...<pos[r];3)dep[ai[i+1]]<=dep[ai[i]]+1。
另xi[i],當i和i+1不是一段是xi[i]=1,所以xi的和+1就是樹高。
條件可以轉化成:1)xi[1]=1;2)如果pos[i]>pos[i+1],xi[i]=1;3)如果ai[i]<ai[i+1],sigma(j=ai[i]~ai[i+1]-1)xi[j]<=1(sigma xi[j]是ai[i]和ai[i+1]的高度差),如果有一個xi確定,其他的是0;如果沒有確定的,會發現pos[ai[i]]<pos[ai[i]+1]<...<pos[ai[i+1]],也就是i<i+1,中間只有一個數,所以期望是0.5。這些約束條件可以用差分維護。
注意:xi是1~n-1的數組,表示i和i+1的關系。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define LD double #define N 200005 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],bi[N],ci[N],pos[N],fi[N]={0},sm[N]={0},xi[N]={0}; int main(){int n,i,j;LD ans=1.;n=in();for (i=1;i<=n;++i) ai[i]=in();for (i=1;i<=n;++i) ci[bi[i]=in()]=i;for (i=1;i<=n;++i){ai[i]=ci[ai[i]];pos[ai[i]]=i;}xi[1]=1;++fi[1];--fi[2];for (i=1;i<n;++i)if (pos[i]>pos[i+1]){xi[i]=1;++fi[i];--fi[i+1];}for (i=1;i<=n;++i) sm[i]=sm[i-1]+xi[i];for (i=1;i<n;++i)if (ai[i]<ai[i+1]){j=sm[ai[i+1]-1]-sm[ai[i]-1];if (j){++fi[ai[i]];--fi[ai[i+1]];}}for (j=0,i=1;i<n;++i){j+=fi[i];if (j) ans+=(LD)xi[i];else ans+=0.5;}printf("%.3f\n",ans-0.001);printf("%.3f\n",ans);printf("%.3f\n",ans+0.001); }View Code
?
轉載于:https://www.cnblogs.com/Rivendell/p/4622742.html
總結
- 上一篇: 中国能进口美国原装超声刀机器吗
- 下一篇: 程序运行时间