[acm/icpc2016香港赛区][Kattis] Slim Cut 支持撤销的动态规划(?)

将边权从大到小排序,若一条边属于两个并查集,则看剩下的并查集划分成两个集合的中小集合最大是多少,用这两个东西来更新答案。

在同一个并查集里的点集不能被划分到两个集合中

至于判断划分成两个集合的中小集合最大是多少,如果用一个朴素的背包动态规划F[i]表示能不能凑到价值为i,那么单次操作是n^2的,总时间代价n^3,显然超时。

考虑任意两个相邻的动态规划,都是将两个元素合并成一个元素。

但是上述的动态规划显然不支持撤销

考虑记录方案数F[i] 为凑到价值i的方案数,对于最后一个物品,有转移方程

for i=n~v

F[i] += F[i-v]

显然撤销转移方程

for i=v~n

F[i] -= F[i-v]

因为这个动态规划物品的顺序是不影响最后的结果,所有对于任意物品的撤销是等价于对于最后一个物品的撤销的

这样每次撤销O(n),新增O(n)

总时间代价O(n^2)

由于方案数可能很多,对1e8+7取模,若为0则认为不存在这种方案


诶这一看就不是正解,但是跑过去了。


#include <iostream>
#include <cstdio>
#include <algorithm>

#define N 15000
#define M 30000
#define mod 100000007

using namespace std;

typedef double db;
struct Edge{ int a,b,v;}e[M];
bool operator < (Edge p1,Edge p2) { return p1.v > p2.v; }
int F[N],fa[N],siz[N],n,m,t,j,tot;
int gf(int x) {
	if (fa[x] == x) return x;
	int t = gf(fa[x]); siz[t] += siz[x]; siz[x] = 0; 
	return fa[x] = t;
}
inline void inc(int &x,int y) { x = x+y>mod ? x+y-mod : x+y; }
inline void dec(int &x,int y) { x = x-y<  0 ? x-y+mod : x-y; }
inline void  ut(int &x,int y) { x = max(x,y); }
inline void pug(int x){ for (int i=x;i<=n;i++) dec(F[i],F[i-x]); }
inline void pig(int x){ for (int i=n;i>=x;i--) inc(F[i],F[i-x]); }

int main() {
	#ifndef ONLINE_JUDGE
		freopen("3.in","r",stdin);
		freopen("3.out","w",stdout);
	#endif
	scanf("%d%d",&n,&m);
	for (int i=1;i<=n;i++) fa[i] = i , siz[i] = 1;
	for (int i=1;i<=m;i++) scanf("%d%d%d",&e[i].a,&e[i].b,&e[i].v);
	for (int i=1;i<=m;i++) e[i].a++ , e[i].b++; 
	sort(e+1,e+m+1); db ans = 1e9; F[0] = 1;
	
	for (int i=1;i<=n;i++)
		for (int j=n;j>=1;j--) inc(F[j],F[j-1]);
		
	for (int i=1;i<=m&&tot<n-1;i++) {
		#define p1 gf(e[i].a)
		#define p2 gf(e[i].b)
		if (p1 != p2) {
			int s1 = siz[p1] , s2 = siz[p2];
			for (t=0,j=0;j<=n;j++) if (F[j]) ut(t,min(j,n-j));
			siz[p2] += siz[p1]; siz[p1] = 0;
			fa[p1] = p2;
			pug(s1); pug(s2); pig(s1+s2);
			tot++;
			if (tot <= n-1) ans = min(ans , (db)e[i].v / (db)t );
		}
	}
		
	printf("%.10lf\n",ans);
	return 0;
}


时间代价极其垃圾对拍暴力程序2^n

#include <iostream>
#include <cstdio>
#define N 10005
using namespace std;
int a[N],b[N],v[N],F[N],n,m,tot;
double ans = 1e9;
void dfs(int dep) {
	if (dep > n) {
		if (tot==0 || tot == n) return ;
		double r=-1;
		double q = min(tot,n-tot);
		for (int i=1;i<=m;i++) if (F[a[i]] ^ F[b[i]]) r=max(r,(double)v[i] / (double) q);
		ans = min(ans,r);
		return ;
	}
	F[dep] = 0; dfs(dep+1);
	F[dep] = 1; tot++; dfs(dep+1); tot--;
}
int main()
{
	#ifndef ONLINE_JUDGE
		freopen("3.in","r",stdin);
		freopen("3_cmp.out","w",stdout);
	#endif
	scanf("%d%d",&n,&m);
	for (int i=1;i<=m;i++) scanf("%d%d%d",&a[i],&b[i],&v[i]);
	for (int i=1;i<=m;i++) a[i]++ , b[i]++;
	dfs(1);
	printf("%.10lf\n",ans);
	return 0;
}


造数据

造的是一个联通图,但可能有重边和自环。所幸暴力和正解都能处理这种情况,也就没去多想了。

#include <iostream>
#include <cstdio>
#include <ctime>
#include <cstdlib>
using namespace std;
const int maxn = 40;
const int maxm = 4000;
const int v = 1e5;
int main() {
	freopen("3.in","w",stdout);
	srand(time(0));
	int n = rand()%maxn+2 , m = rand()%maxm+n-1;
	printf("%d %d\n",n,m);
	for (int i=2;i<=n;i++) 
		printf("%d %d %d\n",i-1,rand()%(i-1)+1-1,rand()*rand()%v+1);
	for (int i=1;i<=m-n+1;i++)
		printf("%d %d %d\n",rand()%n+1-1,rand()%n+1-1,rand()*rand()%v+1);
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值