有麻烦请先笑笑

萩 x H

我的时间很少,但我却有很多想法

2020小米邀请赛决赛补题G,I,J(三DP)

1:G.Rikka with Game Theory
题意:给一个n阶无向图(n<=17),要求你给每个点赋值,使得每个点的值都是与之连接的点的值的mex,求赋值的方案数。
题解:看范围识状压,主要转移和状态是重点,
假设集合V0为赋0的集合,可以推出两个重要条件:
1:显然该集合的所有点不能相连,否则就会有矛盾
2:其他值大于0的点均与该集合里面的点有边存在,否则本身的值就是0.
假设V1为赋1的集合,同样可以推出两个条件
1:本身点不能相连
2:其他值大于1的点均与该集合有边存在
.
.
.
以此类推,就相当于在图上去掉一个个覆盖独立集,直到取完有多少方案。
定义dp[state]代表图为state的时候有多少种方案,枚举state的覆盖独立子图son,则dp[i]+=dp[i^j];

#include<bits/stdc++.h>
using namespace std;
#define f(i,a,b) for(int i=a;i<=b;++i)
#define ll long long
#define N 200005
inline ll read(){ll x;scanf("%lld",&x);return x;}
ll eg[N],dp[N],w[N],n,m;
int main()
{
	dp[0]=1;
	n=read(),m=read();
	f(i,1,m)
	{
		int x=read()-1,y=read()-1;
		eg[x]|=(1<<y);
		eg[y]|=(1<<x);
	}
	ll tot=(1<<n)-1;
	f(i,1,tot) f(j,0,n-1) if(i&(1<<j)) w[i]|=eg[j];
	for(int i=1;i<=tot;++i)
		for(int j=i;j;j=(j-1)&i)
			if(((w[j]&j)==0) && ((w[j]&(i^j))==(i^j))) dp[i]+=dp[i^j];
	cout<<dp[tot];
	return 0;
} 

2:I.Rikka with RCPC
题意:太长了。
题解:显然,我们要尽量避免angry值达到T,因为这样显然在达到T之前就讲题来的更优惠,剩下的节省代价的方案就只剩下条件3了,这道题就转化成去掉所有满足条件3的子串,剩下不能去掉的直接算在答案的贡献上。
定义dp[i]代表到i的最小代价,如果以i结尾不能找到何小于T的子串,就dp[i]=dp[i-1]+a[i];
否则,dp[i]=min(dp[x])(其中x满足pre[i]-pre[x]<=T&&x<=i-k-1);
min(dp[x])用单调队列优化或者线段树维护即可,(用线段树简单多了)
注意数组的最后几个和小于T的数是可以省下的,但是省下不一定答案最优,所以在那几个数附近取dp最小值就行。

#include<cstdio>
#include<iostream>
#define rep(i,s,t) for(int i=s;i<=t;++i)
#define db double
using namespace std;
#define ll long long
const int N=1e6+11;
int n,k,T;
int a[N],q[N];
ll f[N],s[N],ans;
int main(){
	int l,r;
	scanf("%d%d%d",&n,&k,&T);
	++k;
	rep(i,1,n)scanf("%d",a+i),s[i]=s[i-1]+a[i];
	l=r=0;
	rep(i,1,n){
		f[i]=f[i-1];
		if(i>=k){
			while(l!=r&&f[q[r-1]]-s[q[r-1]]<f[i-k]-s[i-k])--r;
			q[r++]=i-k;
		}
		while(l!=r&&s[q[l]]+T<s[i])++l;
		if(l!=r)f[i]=max(f[i],f[q[l]]-s[q[l]]+s[i]);
	}
	ans=s[n];
	rep(i,0,n)
		if(s[n]-s[i]<=T)
			ans=min(ans,s[i]-f[i]);
	cout<<ans<<endl;
	return 0;
}

3:J.Rikka with Book
题意:n本书每本都有重量w[i]和长度l[i],都可以看成二维平面上的一个1l[i]的矩阵,现在要将这些书堆放起来,问在不失去平衡的条件下,书堆的边缘在水平方向上离重心的最远距离。
题解:定义dp[state][i]代表状态为state,底部为i的堆法得到的离重心的最远距离,这样转移就很显然了,枚举底座,设质量m1,然后对于一堆书(质量m2)放在底座上,显然这堆书的中心应该处于底座的边缘,根据常识得,新的重心应该是离底座重心距离为m2/(m1+m2)
l[底]/2的点,就可以转移了。
但是要注意转移后的dp值不一定是放书的那一端,也可能是另一端。

#include<stdio.h>
#include<iostream>
#include<algorithm>
using namespace std;
#define f(i,a,b) for(register int i=a;i<=b;++i)
#define ll long long
#define N 2000005
inline int read(){int x;scanf("%d",&x);return x;}
double dp[N][21],Max[N],W[N],l[23],w[23],Sum[N];
int n;
void counts(int state)
{
	double ans=0;
	for(int i=0;i<n;++i)
		if(state&(1<<i)) Sum[state]++,ans+=w[i];
	W[state]=ans; 
	//cout<<state<<" "<<W[state]<<endl;
}
int main()
{
	n=read();
	f(i,0,n-1) cin>>l[i];        //dp[i]=max(max((dp[old]*dpw[old]+w[j]*(dp[old]+l[j]/2))/dpw[i],dp[i]),((dpw[old]*l[j]+w[j]*l[j]/2)/dpw[i]));
	f(i,0,n-1) cin>>w[i];
	int tot=(1<<n)-1;
	f(i,1,tot) counts(i);
	f(i,1,tot) f(j,0,n-1) if(i&(1<<j))
	{
		if(Sum[i]==1)
		{
			Max[i]=l[j]/2;
			continue;
		}
		double We=W[i^(1<<j)];
		double len=(double)l[j]/2.0*(We/W[i]);
		dp[i][j]=max(Max[i^(1<<j)]+l[j]/2.0-len,l[j]/2.0+len);
		Max[i]=max(Max[i],dp[i][j]);
	}
	printf("%.10lf",Max[tot]);
	return 0;
}

posted @ 2021-01-22 11:36  萩xh  阅读(183)  评论(0编辑  收藏  举报