1. 程式人生 > >17.10.11

17.10.11

沒有 esp names name %d n! 都是 arw 否則

  • 上午
    • BZOJ 1015 [JSOI2008]星球大戰starwar
    • 並查集
      正向考慮的話,感覺不好操作已經合並了的並查集
      但若反向考慮的話,就只用不斷向圖中加入新點——以及合並操作就好了

      代碼:

      #include<cstdio>
      #include<cstring>
      #include<iostream>
      #define MAXN 200005 
      using namespace std;
      struct edge{
      	int to,next;
      }e[MAXN*2];
      int fa[MAXN*2],head[MAXN*2],d[MAXN*2],ans[MAXN*2];
      bool vis[MAXN*2];
      int n,m,cnt,ent=1,k;
      void add(int u,int v){
      	e[ent]=(edge){v,head[u]};
      	head[u]=ent++;
      }
      int find(int x){
      	return x==fa[x]?x:fa[x]=find(fa[x]);
      }
      int main(){
      	scanf("%d%d",&n,&m);
      	for(int i=1;i<=n;i++) fa[i]=i;
      	for(int i=1,a,b;i<=m;i++){
      		scanf("%d%d",&a,&b); a++; b++;
      		add(a,b); add(b,a);
      	}
      	scanf("%d",&k); cnt=n-k;
      	for(int i=1;i<=k;i++) scanf("%d",&d[i]),d[i]++,vis[d[i]]=1;
      	for(int u=1;u<=n;u++) if(!vis[u]){
      		for(int i=head[u];i;i=e[i].next){
      			int v=e[i].to; if(vis[v]) continue;
      			int fu=find(u),fv=find(v);
      			if(fu==fv) continue;
      			cnt--; fa[fv]=fu;
      		}
      	}
      	for(int I=k;I>=1;I--){
      		ans[I]=cnt; 
      		int u=d[I];
      		vis[u]=0; cnt++;
      		for(int i=head[u];i;i=e[i].next){
      			int v=e[i].to; if(vis[v]) continue;
      			int fu=find(u),fv=find(v);
      			if(fu==fv) continue;
      			cnt--; fa[fv]=fu;
      		}
      	}
      	ans[0]=cnt;
      	for(int i=0;i<=k;i++) printf("%d\n",ans[i]);
      	return 0;
      }
    • 車車選講。
  • 下午
    • 車車繼續選講。
    • BZOJ 1016 [JSOI2008]最小生成樹計數
    • 好題。

      有一個性質(正權圖):
      對於一個無向圖的每一種最小生成樹,某種權值的邊的數目是相同的
      (形象點說:如果一個無向圖有兩種最小生成樹的話,且第一種中有2個邊權為5的邊,
      那麽第二種最小生成樹中也一定有2個邊權為5的邊)


      可以通俗一點理解:
      因為對一顆樹來說,邊的個數是固定的,為了保證生成樹的邊權和最小,
      那麽無論是哪一種最小的生成方式,對於某一種權值的邊的數量一定是固定的,否則總邊權就變了。


      正常一點的來理解:
      按照Kruskal算法,


      先考慮權值最小的那些邊,
      這些邊全部放入圖中的話,也許會構成環,
      無論刪掉哪些邊之後使得圖中沒有環,
      最終聯通的點的構成集合都是相同的。
      因為每加一條邊,聯通塊的個數就減少1個,且聯通的點的集合相同,

      所以連的邊的個數相同的,
      並且每種連邊方案的效果(即對圖的聯通貢獻)是相同的(不會受其他權值的邊的影響)。

      至於大一點權值的邊,我們把上面聯通的點縮為一個點,那麽就和上面是一樣的了

      所以每種權值的邊,無論選哪些來連,只要可以聯通成功,那麽所選的該權值的邊的個數就是相同的。

      解法:
      先跑一個Kruskal最小生成樹,統計出每種權值的邊的數量
      接下來枚舉 選出的每種權值記當前枚舉到的權值為w,其對應的選的數量為k),
      把構成最小生成樹的其他權值的邊先聯通它們該連通的那些部分,
      再從權值==w的邊集中暴力枚舉出k個邊嘗試把它們插入圖中,看是否能聯通整個圖,
      如果可以聯通,則表明該權值的這k個邊是可以是一種聯通方法

      統計出 每種權值的邊 有多少種聯通方法
      (因為相同權值的邊不超過10個,狀壓暴力枚舉就好,那個Matrix-Tree什麽的也不會)

      最後把 選出的每種權值 的聯通方法數組合(相乘)就好了。

      代碼:

      #include<cstdio>
      #include<cstring>
      #include<iostream>
      #include<algorithm>
      using namespace std;
      const int mod=31011;
      struct edge{
      	int u,v,w;
      	bool operator <(const edge &rtm) const{
      		return w<rtm.w;
      	}
      }e[1005],use[105];
      struct group{
      	int val,num;
      }g[105];
      int fa[105],ha[1050]; 
      int n,m,sn,cnt,ans=1,p,ent;
      void reset_father(){
      	sn=n;
      	for(int i=1;i<=n;i++) fa[i]=i;
      }
      int find(int x){
      	return fa[x]==x?x:fa[x]=find(fa[x]);
      }
      bool merge(int i,edge *E){
      	int u=E[i].u,v=E[i].v;
      	int fu=find(u),fv=find(v);
      	if(fu==fv) return 0;
      	sn--; fa[fv]=fu;
      	return 1; 
      }
      int doit(int cas){
      	static int l,r,now; now=0;
      	while(!p||e[p].w!=g[cas].val) p++; l=p;
      	while(p<=m&&e[p].w==g[cas].val) p++; r=p-1;
      	for(int s=0;s<(1<<(r-l+1));s++) if(ha[s]==g[cas].num){
      		reset_father();
      		for(int i=1;i<=ent;i++) if(use[i].w!=g[cas].val) merge(i,use);
      		for(int i=0;i<=r-l;i++) if((1<<i)&s) merge(l+i,e);
      		if(sn==1) now++;
      	}
      	return now;
      }
      int main(){
      	for(int i=1<<0;i<=1<<10;i++) 
      		ha[i]=ha[i>>1]+(i&1);
      	scanf("%d%d",&n,&m);
      	for(int i=1;i<=m;i++)
      		scanf("%d%d%d",&e[i].u,&e[i].v,&e[i].w);
      	sort(e+1,e+m+1);
      	reset_father();
      	for(int i=1;i<=m;i++){
      		if(!merge(i,e)) continue;
      		if(!cnt||e[i].w!=g[cnt].val) 
      			++cnt,g[cnt].val=e[i].w;
      		g[cnt].num++;
      		use[++ent]=e[i];
      	}
      	if(sn!=1) {printf("0"); return 0;} 
      	for(int i=1;i<=cnt;i++){
      		int tmp=doit(i);
      		ans=1ll*ans*tmp%mod;
      	}
      	printf("%d",ans);
      	return 0;
      }
  • 晚上
    • BZOJ

17.10.11