zoukankan      html  css  js  c++  java
  • 17.10.11

      • 上午
        • 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
    ",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 1017 [JSOI2008]魔兽地图DotR

      神奇树形dp,
      学习了大佬的方法后,感觉很奇妙......(还可以这么搞)
      (题解看了以后多半很懵,建议看完数组定义和递推后,直接去看代码)

      由于装备的升级呈现树的形式,
      那么按照先处理底层物品,再处理上层物品的顺序去处理数据
      对于物品i,定义
      P[i]表示其伤害,
      M[i]表示其单价(即合成一件物品i的花费),
      L[i]表示其合成数量上限(由M[i]和他的下层物品决定)
      P数组读入就好,M和L数组就在dp时完成

      还有两个数组:
      对于每个dp到的物品,枚举他的合成量l

      f[u][j][k]:表示对u物品所在的子树花费k个金币,且u物品向上层贡献j个(用于上层合成)的最大力量值
      g[tot][j]:表示当前节点(物品)合成l个的情况下,在前tot个儿子里花费j个金币的所获得的最大力量值
      然后用当前物品的下层物品的f数组,推出当前物品的g数组,再用该g数组推出当前物品的f数组。

      由于是一个森林,对每个入度为0的点进行dp,然后在合并他们的贡献。

      剩下的就看代码吧,看看程序的逻辑和实现。

      代码:

      #include<cstdio>
      #include<cstring>
      #include<iostream>
      using namespace std;
      struct edge{
      	int to,val,next;
      }e[55];
      int head[55],in[55];
      int P[55],M[55],L[55];
      int f[55][105][2005],g[55][2005],ans[55][2005];
      int n,m,ent=1,ANS;
      void add(int u,int v,int w){
      	e[ent]=(edge){v,w,head[u]};
      	head[u]=ent++; in[v]++;
      }
      void dp(int u){
      	if(!head[u]){
      		L[u]=min(L[u],m/M[u]);
      		for(int i=0;i<=L[u];i++)
      			for(int j=0;j<=i;j++)
      				f[u][j][M[u]*i]=(i-j)*P[u];
      		return;
      	}
      	L[u]=0x3f3f3f3f;
      	for(int i=head[u];i;i=e[i].next){
      		int v=e[i].to;
      		dp(v);
      		L[u]=min(L[u],L[v]/e[i].val);
      		M[u]+=M[v]*e[i].val;
      	}
      	L[u]=min(L[u],m/M[u]);
      	memset(g,-0x3f,sizeof(g)); g[0][0]=0;
      	for(int l=L[u];l>=0;l--){
      		int tot=0;
      		for(int i=head[u];i;i=e[i].next){
      			int v=e[i].to; tot++;
      			for(int j=0;j<=m;j++)
      				for(int k=0;k<=j;k++)
      					g[tot][j]=max(g[tot][j],g[tot-1][k]+f[v][l*e[i].val][j-k]);
      		}
      		for(int j=0;j<=l;j++)
      			for(int k=0;k<=m;k++)
      				f[u][j][k]=max(f[u][j][k],g[tot][k]+P[u]*(l-j));
      	}
      }
      int main(){
      	memset(f,-0x3f,sizeof(f));
      	scanf("%d%d",&n,&m); char tp; int tot=0;
      	for(int i=1,a,b,c;i<=n;i++){
      		scanf("%d",&P[i]);
      		scanf(" %c",&tp);
      		if(tp=='A'){
      			scanf("%d",&a);
      			for(int j=1;j<=a;j++)
      				scanf("%d%d",&b,&c),add(i,b,c);
      		}
      		else if(tp=='B') scanf("%d%d",&M[i],&L[i]);
      	}
      	for(int i=1;i<=n;i++) if(!in[i]){
      		dp(i);tot++;
      		for(int j=0;j<=m;j++)
      			for(int k=0;k<=j;k++)
      					ans[tot][j]=max(ans[tot][j],ans[tot-1][k]+f[i][0][j-k]);
      	}
      	for(int j=0;j<=m;j++) ANS=max(ANS,ans[tot][j]);
      	printf("%d",ANS);
      	return 0;
      }
      

       

    • 相关阅读:
      文本框样式
      flash载入xml不显示中文之谜
      日期 时间 正则表达式
      .NET对象生命周期小结
      Python标准库12 数学与随机数 (math包,random包)
      CXF 4 应用开发
      CXF 2
      CXF 3
      MyEclipse提示键配置、提示快捷键、提示背景色、关键字颜色、代码显示
      CXF 5参考资料
    • 原文地址:https://www.cnblogs.com/zj75211/p/7652481.html
    Copyright © 2011-2022 走看看