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\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 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;
}

 

转载于:https://www.cnblogs.com/zj75211/p/7652481.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值