AtCoder DP Contest vp 记录

Posted on 2024-03-02 17:02  _XOFqwq  阅读(8)  评论(0编辑  收藏  举报

题单


A

\(i-1\)\(i-2\) 转移即可。

#include<bits/stdc++.h>
using namespace std;

int n;
int h[100031];
int dp[100031];

int main(){
	cin>>n;
	for(int i=1;i<=n;i++) cin>>h[i];
	memset(dp,0x3f,sizeof(dp));
	dp[1]=0;
	for(int i=1;i<=n;i++){
		if(i-1>=1) dp[i]=min(dp[i],dp[i-1]+abs(h[i]-h[i-1]));
		if(i-2>=1) dp[i]=min(dp[i],dp[i-2]+abs(h[i]-h[i-2]));
	}
	cout<<dp[n];
	return 0;
}

B

枚举转移点 \(i-k \sim i-1\) 转移即可。

#include<bits/stdc++.h>
using namespace std;

int n,k;
int h[100031];
int dp[100031];

int main(){
	cin>>n>>k;
	for(int i=1;i<=n;i++) cin>>h[i];
	memset(dp,0x3f,sizeof(dp));
	dp[1]=0;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=k;j++)
			if(i-j>=1) dp[i]=min(dp[i],dp[i-j]+abs(h[i]-h[i-j]));
	cout<<dp[n];
	return 0;
}

C

\(dp_{1 \sim n,0 \sim 3}\) 记录每天三种活动的幸福值即可。

转移:

\[\begin{cases} dp_{i,0}=\max(dp_{i,1},dp_{i,2})+a_{i,0} \\ dp_{i,1}=\max(dp_{i,0},dp_{i,2})+a_{i,1} \\ dp_{i,2}=\max(dp_{i,0},dp_{i,1})+a_{i,2} \\ \end{cases} \]

#include<bits/stdc++.h>
using namespace std;

int n,k;
int a[100031][3];
int dp[100031][3];

int main(){
	cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i][0]>>a[i][1]>>a[i][2];
	memset(dp,-0x3f,sizeof(dp)),dp[0][0]=dp[0][1]=dp[0][2]=0;
	for(int i=1;i<=n;i++){
		dp[i][0]=max(dp[i-1][1],dp[i-1][2])+a[i][0];
		dp[i][1]=max(dp[i-1][0],dp[i-1][2])+a[i][1];
		dp[i][2]=max(dp[i-1][0],dp[i-1][1])+a[i][2];
	}
	cout<<max(dp[n][0],max(dp[n][1],dp[n][2]));
	return 0;
}

D

01 背包板子。

#include<bits/stdc++.h>
#define int long long
using namespace std;

int N,W;
int w[131],v[131];
int dp[100031];

signed main(){
	cin>>N>>W;
	for(int i=1;i<=N;i++) cin>>w[i]>>v[i];
	for(int i=1;i<=N;i++)
		for(int j=W;j>=w[i];j--)
			dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
	cout<<dp[W];
	return 0;
}

E

01 背包变种。

考虑通过 01 背包求出对于所有 \(1 \le i \le \text{MAX}\)\(i\)

在满足 \(\sum v_i \le \text{MAX}\) 的条件下最大化的 \(\sum w_i\)

其中 \(\text{MAX}=n \times 10^3\)(因为 \(1 \le v_i \le 10^3\)),记为 \(dp_i\)

然后倒序枚举所有的 \(dp_i\)(为了保证找到的第一个就是最大的),

若当前的 \(dp_i \le W\),输出即可。

#include<bits/stdc++.h>
#define int long long
using namespace std;

int N,W,MAX;
int w[131],v[131];
int dp[100031];

signed main(){
	cin>>N>>W;
	MAX=N*1000;
	for(int i=1;i<=N;i++) cin>>w[i]>>v[i];
	memset(dp,0x3f,sizeof(dp));
	dp[0]=0;
	for(int i=1;i<=N;i++)
		for(int j=MAX;j>=v[i];j--)
			dp[j]=min(dp[j],dp[j-v[i]]+w[i]);
	for(int i=MAX;i;i--){
		if(dp[i]<=W){
			cout<<i; break;
		}
	}
	return 0;
}

F

LCS 板子。

dp 时可以记录转移节点,然后输出时按转移节点递归输出即可。

#include<bits/stdc++.h>
using namespace std;

string a,b;
int n,m;
int dp[3031][3031];
int x[3031][3031],y[3031][3031];

void print(int nn,int mm){
	if(nn==0||mm==0) return;
	print(nn-x[nn][mm],mm-y[nn][mm]);
	if(a[nn]==b[mm]) cout<<a[nn];
}

int main(){
	cin>>a>>b;
	a="#"+a,b="#"+b;
	n=a.size()-1,m=b.size()-1;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			if(a[i]==b[j]) dp[i][j]=dp[i-1][j-1]+1,x[i][j]=y[i][j]=1;
			else if(dp[i-1][j]>dp[i][j-1]) dp[i][j]=dp[i-1][j],x[i][j]=1;
			else dp[i][j]=dp[i][j-1],y[i][j]=1;
		}
	}
	print(n,m);
	return 0;
}

G

\(dp_x\) 表示从节点 \(x\) 出发的最长路径长度。

转移时,递归地向儿子寻找路径,进行收集型 dp 即可,别忘加上与儿子的连边。顺便对答案取 \(\max\)

#include<bits/stdc++.h>
using namespace std;

int n,m,ans;
int dp[100031];
vector<int> G[100031];

void dfs(int x,int fa){
	if(dp[x]) return;
	dp[x]=-1;
	for(auto i:G[x]){
		if(i==fa) continue;
		dfs(i,x);
		dp[x]=max(dp[x],dp[i]);
	}
	dp[x]++;
	ans=max(ans,dp[x]);
}

int main(){
	cin>>n>>m;
	for(int i=1,u,v;i<=m;i++)
		cin>>u>>v,G[u].push_back(v);
	for(int i=1;i<=n;i++) dfs(i,0);
	cout<<ans;
	return 0;
}

H

从左、上的 . 转移即可。

#include<bits/stdc++.h>
#define int unsigned long long
#define MOD ((int)(1e9+7))
using namespace std;

int n,m,ans;
char a[1031][1031];
int dp[1031][1031];

signed main(){
	cin>>n>>m;
	dp[1][1]=1;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			cin>>a[i][j];
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			if(i==1&&j==1) continue;
			if(a[i-1][j]!='#') dp[i][j]=(dp[i][j]+dp[i-1][j])%MOD;
			if(a[i][j-1]!='#') dp[i][j]=(dp[i][j]+dp[i][j-1])%MOD;
		}
	}
	cout<<dp[n][m]%MOD;
	return 0;
}

I

期望 dp 板子。

\(dp_{i,j}\) 表示前 \(i\) 个银币有 \(j\) 个朝上的概率。

初始:

\[\begin{cases} dp_{0,0}=1\\ dp_{i,0}=dp_{i-1,0} \times (1-p_i) \end{cases} \]

转移:

\[dp_{i,j}=dp_{i-1,j-1} \times p_i + dp_{i-1,j} \times (1-p_i) \]

注意至少要保留 \(10\) 位小数。

#include<bits/stdc++.h>
using namespace std;

int n;
long double ans;
long double p[3031];
long double dp[3031][3031];

int main(){
	cin>>n;
	for(int i=1;i<=n;i++) cin>>p[i];
	dp[0][0]=1;
	for(int i=1;i<=n;i++){
		dp[i][0]=dp[i-1][0]*(1-p[i]);
		for(int j=1;j<=i;j++)
			dp[i][j]=dp[i-1][j-1]*p[i]+dp[i-1][j]*(1-p[i]);
	}
	for(int i=0;i<=n;i++)
		if(i>n-i)
			ans+=dp[n][i];
	cout<<setprecision(10)<<fixed<<ans;
	return 0;
}

J

不好想的期望 dp。

\(dp_{i,j,k}\) 表示有 \(i\)\(1\) 个寿司的盘子、\(j\)\(2\) 个寿司的盘子、\(k\)\(3\) 个寿司的盘子的操作次数的数学期望值(概率与其答案之积的总和)。

转移:

  • 对于空盘子,直接加上每次操作的平均概率 \(\dfrac{n}{i+j+k}\) 即可。

  • 对于 \(1\) 个寿司的盘子,直接加上概率 \(\dfrac{i}{i+j+k} \ \times\) 答案 \(dp_{i-1,j,k}\) 即可。

  • 对于 \(2\) 个寿司的盘子,直接加上概率 \(\dfrac{j}{i+j+k} \ \times\) 答案 \(dp_{i+1,j-1,k}\)(因为 \(2\) 个寿司吃一个就变成了 \(1\) 个) 即可。

  • 对于 \(3\) 个寿司的盘子,直接加上概率 \(\dfrac{k}{i+j+k} \ \times\) 答案 \(dp_{i,j+1,k-1}\) 即可。

答案为 \(dp_{cnt_1,cnt_2,cnt_3}\)\(cnt_i\)\(i\) 个寿司的盘子的初始数量)。

仍然需要至少保留十位小数。

并且为保证无后效性,需要按照 \(k,j,i\) 的顺序枚举状态。

#include<bits/stdc++.h>
using namespace std;

int n;
int cnt[31];
double dp[331][331][331];

int main(){
	cin>>n;
	for(int i=1,a;i<=n;i++) cin>>a,cnt[a]++;
	for(int k=0;k<=n;k++){
		for(int j=0;j<=n;j++){
			for(int i=0;i<=n;i++){
				if(!i&&!j&&!k) continue;
				dp[i][j][k]=(double)n;
				if(i) dp[i][j][k]+=dp[i-1][j][k]*(double)i;
				if(j) dp[i][j][k]+=dp[i+1][j-1][k]*(double)j;
				if(k) dp[i][j][k]+=dp[i][j+1][k-1]*(double)k;
				dp[i][j][k]/=(double)(i+j+k);
			}
		}
	}
	cout<<setprecision(10)<<fixed<<dp[cnt[1]][cnt[2]][cnt[3]];
	return 0;
}

K

博弈论 dp 板子。

\(dp_i\) 表示还剩下 \(i\) 个石子时是先手胜(\(1\))还是后手胜(\(0\))。

显然 \(dp_i\) 可以转移到 \(dp_{i,a_j}\)

很容易想到当一个先手必胜状态可以转移到另一个必败状态时,

当前状态是必败的。

于是我们枚举 \(i,j\) 表示所有状态

并检查其是否会转移到先手必败状态去即可。

#include<bits/stdc++.h>
using namespace std;

int k,n;
int a[131],dp[100031];

int main(){
	cin>>n>>k;
	for(int i=1;i<=n;i++) cin>>a[i];
	for(int i=1;i<=k;i++)
		for(int j=1;j<=n;j++)
			dp[i]+=(i-a[j]>=0&&!dp[i-a[j]]);
	cout<<(dp[k]?"First":"Second");
	return 0;
}

L

有意思的题。似乎既可贪心又可 dp。

很容易想到令 \(dp_{i,j}\) 表示两端数下标为 \(i,j\) 时的 \(X-Y\) 值。

分情况转移:

  • 若已经被取走的数为偶数个,则此时是先手操作,令 \(dp_{i,j}=\max(dp_{i+1,j}+a_i,dp_{i,j-1}+a_j)\)

  • 若已经被取走的数为奇数个,则此时是后手操作,令 \(dp_{i,j}=\min(dp_{i+1,j}-a_i,dp_{i,j-1}-a_j)\)

答案为 \(dp_{1,n}\)

贪心思路有心情再更。

//dp code
#include<bits/stdc++.h>
#define int long long
using namespace std;

int n,a[3031];
int dp[3031][3031];

signed main(){
	cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i];
	for(int l=1;l<=n;l++){
		for(int i=1;i+l-1<=n;i++){
			int j=i+l-1;
			if((n-l)%2) dp[i][j]=min(dp[i+1][j]-a[i],dp[i][j-1]-a[j]);
			else dp[i][j]=max(dp[i+1][j]+a[i],dp[i][j-1]+a[j]);
		}
	}
	cout<<dp[1][n];
	return 0;
}

//greedy code
//......

M

前缀和优化板子。

首先令 \(dp_{i,j}\) 表示前 \(i\) 个人分到 \(j\) 块糖果的方案数。

易得朴素 dp 方程:

\[dp_{i,j}=\sum^{j}_{k=\max(0,j-a_{i-1})} dp_{i-1,k} \]

直接转移时间复杂度 \(O(nk^2)\),无法通过。

考虑到每个状态都是由其上面同列所有行的状态转移而来,

于是令 \(sum_i\) 表示 \(\sum^{i}_{j=0} dp_{i,j}\),转移:

\[sum_i=sum_{i-1}+dp_{i-1,j} \]

然后 dp 转移方程改为:

\[dp_{i,j}=sum_j-sum_{\max(0,j-a_{i-1})-1} \]

即可直接转移。时间复杂度 \(O(nk)\)

#include<bits/stdc++.h>
#define int long long
using namespace std;

const int MOD=1e9+7;
int n,k,a[131];
int sum[100031],dp[131][100031];

int get_sum(int l,int r){
	if(!l) return sum[r]%MOD;
	return (sum[r]+MOD-sum[l-1])%MOD;
}

signed main(){
	cin>>n>>k;
	for(int i=0;i<n;i++) cin>>a[i];
	dp[0][0]=1ll;
	for(int i=1;i<=n;i++){
		sum[0]=dp[i-1][0];
		for(int j=1;j<=k;j++)
			sum[j]=(sum[j-1]+dp[i-1][j])%MOD;
		for(int j=0;j<=k;j++)
			dp[i][j]=get_sum(max(0ll,j-a[i-1]),j);
	}
	cout<<dp[n][k]%MOD;
	return 0;
}

N

原:P1775

区间 dp 板子。

\(dp_{i,j}\) 表示区间 \([i,j]\) 合并成一个数的最小代价。

考虑枚举中转点 \(k\),显然有转移:

\[dp_{i,j}=dp_{i,k}+dp_{k+1,j}+\sum^{j}_{p=i} a_p \]

其中 \(\sum^{j}_{p=i} a_p\) 用前缀和维护即可。

时间复杂度 \(O(n^3)\)

注意枚举 \(i\) 时为确保无后效性需要倒序枚举,枚举 \(k\) 时不能到 \(j\)

#include<bits/stdc++.h>
#define int long long
using namespace std;

int n;
int a[431],sum[431];
int dp[431][431];

signed main(){
	cin>>n;
	for(int i=1;i<=n;i++)
		cin>>a[i],sum[i]=sum[i-1]+a[i];
	memset(dp,0x3f,sizeof(dp));
	for(int i=1;i<=n;i++) dp[i][i]=0;
	for(int i=n;i>=1;i--)
		for(int j=i+1;j<=n;j++)
			for(int k=i;k<j;k++)
				dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]+(sum[j]-sum[i-1]));
	cout<<dp[1][n];
	return 0;
}

O

状压 dp 板子。

考虑对于枚举集合 \(S\),然后枚举 \(i\),让第 \(cur\) 个男人匹配第 \(i\) 个女人。

易得转移方程:

\[dp_S=dp_S+dp_{S-2^{i-1}}(i \in S \ \text{and} \ a_{cur,i}=1) \]

考虑用 \(1 \sim 2^n-1\) 的二进制数表示 \(S\),则转移方程改为:

\[dp_S=dp_S+dp_{S-2^{i-1}}(\lfloor \dfrac{S}{2^{i-1}} \rfloor \bmod 2 =0 \ \text{and} \ a_{cur,i}=1) \]

此时 \(cur\) 即为 \(S\) 二进制下有多少个 \(1\)(因为男女人数相等)。

直接转移即可,答案为 \(dp_{1,2^n-1}\),时间复杂度 \(O(n2^n)\)

#include<bits/stdc++.h>
using namespace std;

const int MOD=1e9+7;
int n;
int a[31][31];
int dp[(1<<21)];

int main(){
	cin>>n;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
			cin>>a[i][j];
	dp[0]=1;
	for(int S=1;S<(1<<n);S++){
		int cur=0,S2=S;
		while(S2>0) cur+=(S2&1),S2>>=1;
		for(int i=1;i<=n;i++)
			if(a[cur][i]&&((S>>(i-1))&1))
				dp[S]=(dp[S]+dp[S-(1<<(i-1))])%MOD;
	}
	cout<<dp[(1<<n)-1]%MOD;
	return 0;
}

P

树形 dp 板子。

\(dp_{i,0/1}\) 表示将节点 \(i\) 及其子树染成黑(\(1\))或白(\(0\))的方案数。

显然有转移:

\[dp_{i,0}=dp_{i,0} \times (dp_{j,0}+dp_{j,1}) \]

\[dp_{i,1}=dp_{i,1} \times dp_{j,0} \]

(其中 \(j \in i\) 的子树)

直接 dfs 进行转移即可。时间复杂度 \(O(n)\)

#include<bits/stdc++.h>
#define int long long
using namespace std;

int n;
vector<int> G[200031];
int dp[100031][2];
const int MOD=1e9+7;

void dfs(int x,int f){
	dp[x][0]=dp[x][1]=1;
	for(auto i:G[x]){
		if(i==f) continue;
		dfs(i,x);
		dp[x][0]=(dp[x][0]*(dp[i][0]+dp[i][1]))%MOD;
		dp[x][1]=(dp[x][1]*dp[i][0])%MOD;
	}
}

signed main(){
	cin>>n;
	for(int i=1,u,v;i<n;i++)
		cin>>u>>v,
		G[u].push_back(v),
		G[v].push_back(u);
	dfs(1,0);
	cout<<(dp[1][0]+dp[1][1])%MOD;
	return 0;
}

Q

题目要求出最大带权上升子序列(Maximum Weighted Increasing Subsequence,我们姑且称其为 MWIS)。

\(dp_i\) 表示以 \(i\) 结尾的 MWIS 的长度。显然有转移:

\[dp_i=\max^{i-1}_{j=1}\{dp_j+a_i \mid h_j<h_i\} \]

套个树状数组优化取 \(\max\) 再进行转移即可,时间复杂度 \(O(n \log n)\)

#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,ans,a[200031],h[200031],t[200031];
void add(int x,int y){ for(int i=x;i<=n;i+=i&(-i)) t[i]=max(t[i],y); }
int qry(int x){ int q=0; for(int i=x;i;i-=i&(-i)) q=max(q,t[i]); return q; }
signed main(){
	cin>>n;
	for(int i=1;i<=n;i++) cin>>h[i];
	for(int i=1;i<=n;i++) cin>>a[i];
	for(int i=1,dp;i<=n;i++) dp=qry(h[i]-1)+a[i],add(h[i],dp),ans=max(ans,dp);
	cout<<ans;
	return 0;
}

R

矩阵加速 dp 板子。

首先令 \(dp_{i,j,k}\) 表示 \(i \to j\) 路径中长度为 \(k\) 的路径条数。

考虑像 floyd 那样枚举中转点 \(t\) 进行转移。

于是易得转移方程:

\[dp_{i,j,k}=\sum^{n}_{t=1} dp_{i,t,k-1}+dp_{t,j,1} \]

直接转移时间复杂度 \(O(n^3k)\),T 飞了。

然后我们发现这个转移方程就是矩阵快速幂。

于是套个矩阵快速幂即可。时间复杂度 \(O(n^3 \log k)\)

#include<bits/stdc++.h>
#define int long long
using namespace std;

int n,k,ans;
const int MOD=1e9+7;
struct matrix{
	int siz,v[53][53];
	matrix(){ memset(v,0,sizeof(v)); }
}a;

matrix operator* (const matrix &a,const matrix &b){
	matrix ret; ret.siz=a.siz;
	for(int i=1;i<=ret.siz;i++)
		for(int j=1;j<=ret.siz;j++)
			for(int t=1;t<=ret.siz;t++)
				ret.v[i][j]+=a.v[i][t]*b.v[t][j]%MOD,ret.v[i][j]%=MOD;
	return ret;
}
matrix qpow(matrix a,int b){
	matrix ret; ret.siz=a.siz;
	for(int i=1;i<=ret.siz;i++) ret.v[i][i]=1;
	while(b){
		if(b&1) ret=ret*a;
		a=a*a,b>>=1;
	}
	return ret;
}

signed main(){
	cin>>n>>k,a.siz=n;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
			cin>>a.v[i][j];
	matrix res=qpow(a,k);
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
			ans=(ans+res.v[i][j])%MOD;
	cout<<ans;
	return 0;
}

S

数位 dp 板子。

\(dp_{i,j,k}\) 表示 \(1 \sim K\) 中,目前处于前缀 \(i\) 位,模 \(d\) 余数为 \(j\),且与 \(K\) 相同(\(k=1\)) / 不同(\(k=0\))的数的个数。

显然有转移:

\[dp_{i,j,k}=\sum^{m}_{t=0} dp_{i-1,(j+t) \bmod d,k \&(t=m)} \]

(其中 \(m\) 为当前这一位能填的最大数值,\(t\) 枚举的是所有能填的数,枚举顺序是从高位到低位)

直接转移即可。需要注意一些细节(在代码中)。

#include<bits/stdc++.h>
#define int long long
using namespace std;

string k;
int d,num[10031];
int dp[10031][131][2];
const int MOD=1e9+7;

int dfs(int pos,int res,int sta){
	if(!pos) return !res;
	if(dp[pos][res][sta]!=(int)-1) return dp[pos][res][sta]%MOD;
	int ret=0,maxx=9;
	if(sta) maxx=num[pos]; //保证构造出的数 <=k
	for(int i=0;i<=maxx;i++)
		ret=(ret+dfs(pos-1,(res+i)%d,sta&&(i==maxx)))%MOD;
	return dp[pos][res][sta]=ret;
}

signed main(){
	cin>>k>>d;
	memset(dp,-1,sizeof(dp));
	for(int i=0;i<k.size();i++) num[i+1]=k[k.size()-i-1]-'0';
	cout<<((dfs(k.size(),0,1)-1)%MOD+MOD)%MOD; //要 -1,因为 0 也被算入了
	return 0;
}

T

简单前缀和优化。

\(dp_{i,j}\) 表示第 \(i\) 个数填 \(j\) 时的方案数。

答案即为 \(\sum_{k=1}^{n} dp_{i,k}\)

易得朴素转移方程:

\[\begin{cases} dp_{i,j}=\sum_{k=1}^{j-1} dp_{i,k} \ \ \ s_i=\texttt{<}\\ dp_{i,j}=\sum_{k=j}^{i-1} dp_{i,k} \ \ \ s_i=\texttt{>}\\ \end{cases} \]

套个前缀和优化掉 \(\sum\) 即可。时间复杂度 \(O(n^2)\)

#include<bits/stdc++.h>
#define int long long
using namespace std;

int n;
string s;
int dp[3031][3031];
int sum[3031][3031];
const int MOD=1e9+7;

signed main(){
	cin>>n>>s,s="#"+s;
	dp[1][1]=1;
	for(int i=1;i<=n;i++) sum[1][i]=1;
	for(int i=2;i<=n;i++){
		for(int j=1;j<=i;j++){
			if(s[i-1]=='<') dp[i][j]=sum[i-1][j-1]%MOD;
			else dp[i][j]=(sum[i-1][i-1]-sum[i-1][j-1]+MOD)%MOD;
			sum[i][j]=(sum[i][j-1]+dp[i][j])%MOD;
		}
	}
	cout<<sum[n][n]%MOD;
	return 0;
}

U

看到 \(n \le 16\) 直接状压 dp。

\(dp_i\) 表示集合 \(i\) 能产生的最大得分。

易得转移方程:

\[dp_i=\max_{j \in i} \{ dp_j+dp_k \} \]

(其中 \(k\)\(j\) 的补集)

初始状态预处理出每个集合 \(i\) 分成一组的最大得分即可。

#include<bits/stdc++.h>
#define int long long
using namespace std;

int n;
int a[31][31];
int val[(1<<17)],dp[(1<<17)];

signed main(){
	cin>>n;
	for(int i=0;i<n;i++)
		for(int j=0;j<n;j++)
			cin>>a[i][j];
	for(int S=1;S<(1<<n);S++)
		for(int i=0;i<n;i++)
			for(int j=0;j<i;j++)
				if(((1<<i)&S)&&((1<<j)&S))
					val[S]+=a[i][j];
	for(int S=1;S<(1<<n);S++){
		dp[S]=val[S];
		for(int i=S&(S-1);i;i=S&(i-1))
			dp[S]=max(dp[S],dp[i]+dp[S^i]);
	}
	cout<<dp[(1<<n)-1];
	return 0;
}

V

换根 dp 板子。

\(dp1_u\) 表示在 \(u\) 的子树内,黑节点与 \(u\) 组成连通块的方案数;\(dp2_u\) 则是在子树外。

对于 \(dp1_u\),显然有转移方程:

\[dp1_u=\prod dp1_v+1(v \in \text{son}\ u) \]

对于 \(dp2_u\),我们考虑将问题转化为求父节点 \(f\) 子树内的方案数,易得转移方程:

\[dp2_u=(dp_{f} \times \prod dp_v(v \in \text{brother}\ u)+1)+1 \]

对于 \(\prod dp_v(v \in \text{brother}\ u)+1\) 预处理前 / 后缀积即可直接转移。

时间复杂度 \(O(n)\)

#include<bits/stdc++.h>
using namespace std;

int n,MOD;
vector<int> G[100031];
int dp1[100031],dp2[100031];
int pre[100031],lst[100031];

void dfs1(int x,int fa){
	vector<int> son; dp1[x]=1;
	for(auto i:G[x]){
		if(i==fa) continue;
		dfs1(i,x);
		dp1[x]=1ll*dp1[x]*(dp1[i]+1)%MOD;
		son.push_back(i);
	}
	int npre=1,nlst=1;
	for(int i=0;i<son.size();i++)
		pre[son[i]]=npre,npre=1ll*npre*(dp1[son[i]]+1)%MOD;
	for(int i=son.size()-1;i>=0;i--)
		lst[son[i]]=nlst,nlst=1ll*nlst*(dp1[son[i]]+1)%MOD;
}
void dfs2(int x,int fa){
	if(fa==-1) dp2[x]=1;
	else dp2[x]=(1ll*dp2[fa]*(1ll*pre[x]*lst[x]%MOD)%MOD+1)%MOD;
	for(auto i:G[x])
		if(i!=fa) dfs2(i,x);
}

signed main(){
	cin>>n>>MOD;
	for(int i=1,u,v;i<n;i++)
		cin>>u>>v,G[u].push_back(v),G[v].push_back(u);
	dfs1(1,-1),dfs2(1,-1);
	for(int i=1;i<=n;i++) cout<<1ll*dp1[i]*dp2[i]%MOD<<'\n';
	return 0;
}

W

\(dp_{i,j}\) 表示前 \(i\) 个数中最后一个 \(1\) 在位置 \(j\) 时能获得的最大分数。

易得转移:

\[dp_{i,j}=dp_{i-1,j}+\sum_{l_k \le j,r_k=i} v_k \]

可以发现在转移过程中可能有同一个规则被重复使用,

于是我们考虑转移时对于每条规则将其相关联的转移一并操作。

因为规则影响的是连续区间,所以可以使用线段树维护,

具体地,我们每次令 \(dp_i\) 初始化为前 \(i-1\) 的最大 \(dp\) 值,

然后运用双指针思想枚举每个满足 \(r_j=i\) 的规则,转移即可。

每次转移均摊 \(O(1)\),总时间复杂度 \(O(n \log n)\)

#include<bits/stdc++.h>
#define int long long
#define ll(p) (p<<1)
#define rr(p) (p<<1|1)
using namespace std;

int n,m;
struct interval{
	int l,r,v;
}a[200031];
struct Segment_tree{
	int tag,val;
}t[800031];

bool cmp(interval x,interval y){ return x.r<y.r; }

void spd(int rt){
	t[ll(rt)].val+=t[rt].tag,t[rr(rt)].val+=t[rt].tag;
	t[ll(rt)].tag+=t[rt].tag,t[rr(rt)].tag+=t[rt].tag,t[rt].tag=0;
}
void upd(int rt,int l,int r,int s,int e,int v){
	if(l>=s&&r<=e){
		t[rt].val+=v,t[rt].tag+=v; return;
	}
	spd(rt); int mid=(l+r)>>1;
	if(s<=mid) upd(ll(rt),l,mid,s,e,v);
	if(e>mid) upd(rr(rt),mid+1,r,s,e,v);
	t[rt].val=max(t[ll(rt)].val,t[rr(rt)].val);
}

signed main(){
	cin>>n>>m;
	for(int i=1;i<=m;i++) cin>>a[i].l>>a[i].r>>a[i].v;
	sort(a+1,a+m+1,cmp);
	for(int i=1,j=1;i<=n;i++){
		upd(1,1,n,i,i,max(0ll,t[1].val));
		while(a[j].r==i&&j<=m) upd(1,1,n,a[j].l,a[j].r,a[j].v),j++;
	}
	cout<<max(0ll,t[1].val);
	return 0;
}

X

一眼 01 背包。但是直接做会 T。

于是根据国王游戏那题想到对于每个箱子按照 \(s_i+w_i\) 从小到大排序,

然后就过了。

#include<bits/stdc++.h>
#define int long long
using namespace std;

int n,ans=-1e9;
int dp[20031];
struct node{
	int w,s,v;
}a[1031];

bool cmp(node x,node y){
	return x.s+x.w<y.s+y.w;
}

signed main(){
	cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i].w>>a[i].s>>a[i].v;
	sort(a+1,a+n+1,cmp);
	memset(dp,-1,sizeof(dp)),dp[0]=0;
	for(int i=1;i<=n;i++)
		for(int j=a[i].s;j>=0;j--)
			dp[j+a[i].w]=max(dp[j+a[i].w],dp[j]+a[i].v);
	for(int i=0;i<=20000;i++) ans=max(ans,dp[i]);
	cout<<ans;
	return 0;
}

Y

Solution link

Z

Solution link


耗时 2 个月,终于更完了 qwq。