区域赛dp题范做

2020昆明站B题
题目描述:
给定一个目标的矩形区域,要求把这个区域全部染成黑色,再给出 n n n( n n n < = 10 <=10 <=10)个其他的矩形,每次从 n n n个当中等可能选择一个把选择出的矩形区域染成黑色,问把目标矩形全部染成黑色的期望次数
思路:
根据题意,选择其中一些矩形的集合 S S S是可以达成染色目的的,那么我们需要计算的就是选出这些矩形集合的期望次数,对于集合组成我们用二进制数表示,0代表未选,1代表选,另 d p [ x ] dp[x] dp[x]为在状态 x x x下到目标状态的期望次数,那么 d p [ x ] x ∈ S = 0 dp[x]_{x\in S}=0 dp[x]xS=0,考虑状态转移,假设当前抽到的矩形是第 j j j位,如果 j ⊆ x j\subseteq x jx,那么 d p [ x ] = = d p [ x ] + 1 dp[x]==dp[x]+1 dp[x]==dp[x]+1,否则 d p [ x ] = = d p [ x ∣ ( 1 < < j ) ] + 1 dp[x]==dp[x|(1<<j)]+1 dp[x]==dp[x(1<<j)]+1,又因为抽到矩形的可能是相等的,所以我们可以把上式化简为:
d p [ x ] = 1 + c n t n × d p [ x ] + 1 n × sum ⁡ dp[x]=1+\frac{c n t}{n} \times d p[x]+\frac{1}{n} \times \operatorname{sum} dp[x]=1+ncnt×dp[x]+n1×sum (sum表示所有 d p [ x ∣ 1 < < j ] d p[x \mid 1<<j] dp[x1<<j] 的和, c n t c n t cnt 表示 x x x 中 1 的个数),把 d p [ x ] dp[x] dp[x]移到同一边,可得 d p [ x ] = n + s u m n − c n t d p[x]=\frac{n+s u m}{n-c n t} dp[x]=ncntn+sum,从大到小转移即可。
对于可行集合 S S S的计算,我们观察到 n < = 10 n<=10 n<=10,那么我们可以把坐标离散化,然后dfs枚举是否选择当前矩形,看覆盖区域的大小是否和目标矩形重合集合,重合代表当前状态即属于 S S S,复杂度是 O ( 2 n ∗ n 2 ) O(2^{n}* n^{2}) O(2nn2)的。

#include<bits/stdc++.h>
#define int long long
#define io ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
using namespace std;
const int maxn=15;
const int inf=1e9+7;
const int mod=998244353;
int n,w,h;
int x1[maxn];
int y11[maxn];
int x2[maxn];
int y2[maxn];
int inv[maxn];
vector<int>xx,yy;
int findx(int x){
	return lower_bound(xx.begin(),xx.end(),x)-xx.begin();
}
int findy(int x){
	return lower_bound(yy.begin(),yy.end(),x)-yy.begin();
}
int st[1<<10],dp[1<<10];
int vis[maxn][maxn];
int cnt; 
int flag;
void dfs(int now,int zt){  //现在判断到了第now个目前状态为zt
	if(now==n){
		if(cnt==w*h){
			st[zt]=1;
			flag=1;
		}
		else{
			st[zt]=0;
		}
		return;
	}
	dfs(now+1,zt);  //代表不选择第now个
	
	for(int i=x1[now];i<x2[now];i++){
		for(int j=y11[now];j<y2[now];j++){
			vis[i][j]++;
			if(vis[i][j]==1){
				cnt++;
			}
		}
	}
	dfs(now+1,zt|(1<<now)); //代表选择第now个
	for(int i=x1[now];i<x2[now];i++){
		for(int j=y11[now];j<y2[now];j++){
			vis[i][j]--;
			if(vis[i][j]==0){
				cnt--;
			}
		}
	}
}
void solve(){
	xx.clear();
	yy.clear();
	memset(vis,0,sizeof(vis));
	memset(st,0,sizeof(st));
	memset(dp,0,sizeof(dp));
	flag=0;
	cnt=0;
	cin>>n>>w>>h;
	xx.push_back(0);
	yy.push_back(0);
	xx.push_back(w);
	yy.push_back(h);
	for(int i=0;i<n;i++){
		cin>>x1[i]>>y11[i]>>x2[i]>>y2[i];
		x1[i]=min(x1[i],w);
		x2[i]=min(x2[i],w);
		y11[i]=min(y11[i],h);
		y2[i]=min(y2[i],h);
		xx.push_back(x1[i]);
		xx.push_back(x2[i]);
		yy.push_back(y11[i]);
		yy.push_back(y2[i]);
	}
	sort(xx.begin(),xx.end());
	sort(yy.begin(),yy.end());
	xx.erase(unique(xx.begin(),xx.end()),xx.end());
	yy.erase(unique(yy.begin(),yy.end()),yy.end());
	for(int i=0;i<n;i++){
		x1[i]=findx(x1[i]);
		y11[i]=findy(y11[i]);
		x2[i]=findx(x2[i]);
		y2[i]=findy(y2[i]);
	}
	w=findx(w);
	h=findy(h);
	//dfs一下看哪些状态是可行的
	dfs(0,0);
	if(!flag){
		cout<<"-1"<<"\n";
		return;
	}
	for(int i=(1<<n)-1;i>=0;i--){
		if(st[i]){
			continue;
		}
		else{
			int tt=0,res=0;
			for(int j=0;j<n;j++){
				if((i>>j)&1){
					tt++;
				}
				else{
					res+=dp[i|(1<<j)];
					res%=mod;
				}
			}
			dp[i]=(n+res)%mod*inv[n-tt]%mod;
		}
	}
	cout<<dp[0]<<"\n";
}
int ksm(int x,int n){
	int ans=1;
	while(n){
		if(n&1){
			ans=ans*x%mod;
		}
		x=x*x%mod;
		n>>=1;
	}
	return ans;
}
signed main(){
	for(int i=0;i<=10;i++){
		inv[i]=ksm(i,mod-2);
	}
	int t=1;
	cin>>t;
	while(t--){
		solve();
	}
}

2021沈阳站L题
题目描述:
给定 n n n个节点的完全图,再给出 2 n − 1 2n-1 2n1条边,这 2 n − 1 2n-1 2n1条边形成的是一颗树,题目要求在不选择这 2 n − 1 2n-1 2n1条边的情况下形成完美匹配的方案个数。
思路:
首先我们考虑 2 n 2n 2n个点的完全图如果没有选择边的限制形成完美匹配的数量是多少,首先把 2 n 2n 2n个点分为 2 2 2组各为 n n n个点的情况有 C 2 n n C_{2n}^{n} C2nn种,然后对于其中一组的 n n n个点,第一个点可以选择另外一组的任意 n n n个点之一连边,第二个点可以选择另外一组的任意 n − 1 n-1 n1个点连边…,即为 n ! n! n!,但是完美匹配是关于边的,如果一条边连接的两个点没有变化那么就是同一组匹配,但是这里我们每一条边都计算了 2 2 2次所以最后需要除以 2 n 2^{n} 2n,即为 C 2 n n ∗ n ! 2 n \frac{C_{2n}^{n}*n! }{2^{n} } 2nC2nnn!,化简即为 2 n ! n !   ∗ 2 n \frac{2n!}{n!~*2^{n} } n! 2n2n!

然后考虑容斥,题目要求不用这 2 n − 1 2n-1 2n1条边,那么容斥一下,答案=没有限制的匹配数-在 2 n − 1 2n-1 2n1条边已经选了 1 1 1条边构成完美匹配的方案数+已经选了 2 2 2条边构成完美匹配的方案数-已经选了 3 3 3条边构成完美匹配的方案数…
然后我们要求出包含树边的所有匹配方案,这个可以用树形dp来求:

状态表示: 设 d p [ u ] [ i ] [ 0 / 1 ] dp[u][i][0/1] dp[u][i][0/1] 表示以点 u u u为根节点的子树中,有 i i i个匹配并且 u u u节点是否参与匹配的方案数

状态转移:对于一个以 u u u为根的节点,枚举其每个子节点 v v v状态转移有三种情况:

  • 合 并 u 与 其 子 树 v ,并 且 不 选 u
  • 合 并 u 与 其 子 树 v ,选 u
  • 合 并 u 与 其 子 树 v ,并 且 加 上 一 个 u − v 的 匹 配

然后对于每种情况分别考虑转移方程即可

#include<bits/stdc++.h>
#define int long long
#define io ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
using namespace std;
const int maxn=4e3+5;
const int inf=1e9+7;
const int mod=998244353;;
int p2[maxn];
int sz[maxn];
int fac[maxn];
vector<int>g[maxn];
int dp[maxn][maxn][2];
int f[maxn][2];
int ksm(int x,int n){
	int ans=1;
	while(n){
		if(n&1){
			ans=ans*x%mod;
		}
		x=x*x%mod;
		n>>=1;
	}
	return ans;
}
void dfs(int u,int fa){
	sz[u]=1;
	dp[u][0][0]=1;
	//以u为根节点的子树 选择了0条删除了的边 不选u的情况下的方案数
	for(int v:g[u]){
		if(v==fa){
			continue;
		}
		dfs(v,u);
		memset(f,0,sizeof(f));
		for(int i=sz[u]/2;i>=0;i--){
			for(int j=sz[v]/2;j>=0;j--){
				f[i + j][0] = (f[i + j][0] + dp[u][i][0] * (dp[v][j][0] + dp[v][j][1]) % mod) % mod;
				f[i + j][1] = (f[i + j][1] + dp[u][i][1] * (dp[v][j][0] + dp[v][j][1]) % mod ) % mod;
				f[i + j + 1][1] = (f[i + j + 1][1] + dp[u][i][0] * dp[v][j][0] % mod) % mod;
			}
		}
		sz[u]+=sz[v];
		for(int i=0;i<=sz[u]/2;i++){
			dp[u][i][0] = f[i][0], dp[u][i][1] = f[i][1];
		}
	}
}
int get(int x)
{
	return (fac[2 * x] * ksm(fac[x], mod - 2) % mod * ksm(p2[x], mod - 2) % mod);
}
void solve(){
	int n;
	cin>>n;
	p2[0]=1;
	fac[0]=1;
	for(int i=1;i<=2*n;i++){
		p2[i] = (p2[i - 1] * 2) % mod, fac[i] = (fac[i - 1] * i) % mod;
	}
	for(int i=1;i<=2*n-1;i++){
		int u,v;
		cin>>u>>v;
		g[u].push_back(v);
		g[v].push_back(u);
	}
	dfs(1,0);
	int sum=get(n);
	for(int i = 1; i <= n; i ++ )
	{
		int num = (dp[1][i][0] + dp[1][i][1]) % mod * get(n - i) % mod;
		if(i & 1) sum = (sum - num + mod) % mod;
		else sum = (sum + num) % mod;
	}
	cout<<sum<<"\n";
}
signed main(){
	int t=1;
	//cin>>t;
	while(t--){
		solve();
	}
}

2022澳门站G题
题目描述:
给定一个 1 − n 1-n 1n的循环排列,然后有一个读取器,每次可以从前排列的前 k k k个里面任意读取,现在要求从 1 − n 1-n 1n依次读取,你可以把这个循环排列左移或者右移,要求最小次数是多少
思路:
首先如果当前给了你这个排列,我们肯定先看前 k k k个,看最多可以从 1 读到 x 1读到x 1读到x x x x是哪个,那么我们下一步就需要把 x + 1 x+1 x+1给移动过来,移到哪里呢?肯定要移动到 1 1 1或者 k k k的位置,那我们怎么知道移动到哪里好呢?这个贪心是没法解决的,得通过 d p dp dp解决。因为这个排列是确定的所以你把 x + 1 x+1 x+1移动到 1 1 1或者 k k k位置之后前 k k k个数也就确定了,那么我们重复一下之前的操作,再看最多可以从 x + 1 读到 y x+1读到y x+1读到y y y y是哪个,这个可以通过区间mex预处理解决,记 i i i作为左边第一个的时候目前要查询的数为 l [ i ] l[i] l[i],记 i i i作为第 k k k个的时候目前要查询的数为 r [ i ] r[i] r[i],记 s u m sum sum为需要的最小移动次数,那么状态转移即为
d p [ l [ i ] ] [ 0 ] = m i n ( d p [ l [ i ] ] [ 0 ] , d p [ i ] [ 0 ] + s u m ( p o s [ i ] , p o s [ l [ i ] ] ) ) dp[l[i]][0]=min(dp[l[i]][0],dp[i][0]+sum(pos[i],pos[l[i]])) dp[l[i]][0]=min(dp[l[i]][0],dp[i][0]+sum(pos[i],pos[l[i]]))
d p [ l [ i ] ] [ 1 ] = m i n ( d p [ l [ i ] ] [ 1 ] , d p [ i ] [ 0 ] + s u m ( p o s [ i ] + k − 1 , p o s [ l [ i ] ] ) ) dp[l[i]][1] = min(dp[l[i]][1], dp[i][0] + sum(pos[i] + k - 1, pos[l[i]])) dp[l[i]][1]=min(dp[l[i]][1],dp[i][0]+sum(pos[i]+k1,pos[l[i]]))
可见,只要把区间mex给解决之后,dp转移复杂度是 O ( n ) O(n) O(n)

#include<bits/stdc++.h>
#define int long long
#define io ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
using namespace std;
const int maxn=1e6+10;
const int INF=1e18;
const int mod=1e9+7;
int n,a[maxn],k,dp[maxn][2],l[maxn],r[maxn],pos[maxn];
struct node{
	int l,r,minv;
}tr[maxn<<2];
void pushup(int u)
{
	tr[u].minv = min(tr[u << 1].minv, tr[u << 1 | 1].minv);
}

void modify(int u, int x,int v)
{
	if(tr[u].l == tr[u].r)
	{
		tr[u].minv = v;
		return;
	}
	
	int mid = tr[u].l + tr[u].r >> 1;
	if(x <= mid) modify(u << 1, x, v);
	else modify(u << 1 | 1, x, v);
	pushup(u);
}

int query(int u, int l, int r)
{
	if(l <= tr[u].l && tr[u].r <= r) return tr[u].minv;
	
	int mid = tr[u].l + tr[u].r >> 1, minv = INF;
	pushup(u);
	if(l <= mid) minv = min(minv, query(u << 1, l, r));
	if(r > mid) minv = min(minv, query(u << 1 | 1, l, r));
	return minv;
}

int query_mex(int l, int r)
{
	if(r > n) r -= n;
	if(l < 1) l += n;
	if(l <= r)
	{
		int minv = INF;
		if(l != 1) minv = min(minv, query(1, 1, l - 1));
		if(r != n) minv = min(minv, query(1, r + 1, n));
		return minv;
	}
	else
		return query(1, r + 1, l - 1);
}

void build(int u, int l, int r)
{
	if(l == r)
	{
		tr[u] = {l, r, a[l]};
		return;
	}
	tr[u].l = l, tr[u].r = r;
	int mid = l + r >> 1;
	build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
	pushup(u);
}

int sum(int l, int r)
{
	if(l < 1) l += n;
	if(l > n) l -= n;
	return min({abs(r - l), n - abs(r - l)});
}
void solve(){
	cin>>n>>k;
	for(int i=1;i<=n;i++){
		cin>>a[i];
		pos[a[i]]=i;
	}
	if(k==n){
		cout<<"0"<<"\n";
		return;
	}
	build(1,1,n);
	int t=query_mex(1,k);  //第一次需要移动的数
	dp[t][0] = sum(1, pos[t]), dp[t][1] = sum(k, pos[t]);   
	for(int i = 1; i <= n; i ++ ){
		//预处理出l[i]和r[i]
		if(i != t)
			dp[i][0] = dp[i][1] = INF;
		if(i < t)
			modify(1, pos[i], INF);
		else 
		{
			l[i] = query_mex(pos[i], pos[i] + k - 1);
			r[i] = query_mex(pos[i] - k + 1, pos[i]);
			modify(1, pos[i], INF);
		}
	}
	int ans=INF;
	for(int i=t;i<=n;i++){
		if(l[i]!=INF){
			//i作为左边第一个的时候目前要查询的数为l[i]
			//当前要把l[i]移动到左边第一个需要的最小次数
			dp[l[i]][0]=min(dp[l[i]][0],dp[i][0]+sum(pos[i],pos[l[i]]));
			dp[l[i]][1] = min(dp[l[i]][1], dp[i][0] + sum(pos[i] + k - 1, pos[l[i]]));
		}
		else{
			ans=min(ans,dp[i][0]);  //说明没有数了
		}
		if(r[i]!=INF){
			dp[r[i]][0] = min(dp[r[i]][0], dp[i][1] + sum(pos[i] - k + 1, pos[r[i]]));
			dp[r[i]][1] = min(dp[r[i]][1], dp[i][1] + sum(pos[i], pos[r[i]]));
		}
		else{
			ans=min(ans,dp[i][1]);
		}
	}
	cout<<ans<<"\n";
}
signed main(){
	int t=1;
	cin>>t;
	while(t--){
		solve();
	}
}

2021南京站H题
题目描述:
给你一颗 n n n个节点的树,每个节点都有一个val值的蝴蝶和一个 t t t值( 1 < = t < = 3 1<=t<=3 1<=t<=3), t t t值代表如果你到了 x x x节点的父亲节点,你就惊扰到了 x x x节点上面的蝴蝶,再经过 t t t时刻后蝴蝶会一瞬间全部飞走,问在最佳策略下能得到的最多蝴蝶数是多少
思路:
经典的树形dp,首先我们观察到 1 < = t < = 3 1<=t<=3 1<=t<=3,在 x x x节点的 t = = 1 或者 t = = 2 t==1或者t==2 t==1或者t==2的时候就意味着你要嘛直接从父节点往 x x x走,这样能抓到上面的蝴蝶,要嘛就抓不到,在 t = = 3 t==3 t==3的时候意味着你可以先去另外一个儿子节点 y y y y y y上面的蝴蝶再立即返回到 f a fa fa节点来 x x x节点抓 x x x的蝴蝶,这样的时候 x x x节点的所有儿子节点蝴蝶也都抓不到了,用 d p 1 [ i ] , d p 2 [ i ] , d p 3 [ i ] dp1[i],dp2[i],dp3[i] dp1[i],dp2[i],dp3[i]分别代表以 i i i为根的子树走到 i i i节点拿了 i i i节点的蝴蝶时子树的最大价值,走到 i i i节点没拿 i i i节点的蝴蝶时子树的最大价值,以及走到 i i i节点拿了 i i i节点的蝴蝶但是 i i i的儿子节点的蝴蝶全没拿的最大价值,然后转移即可

#include<bits/stdc++.h>
#define int long long
#define io ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
using namespace std;
const int maxn=2e5+5;
const int inf=1e9+7;
const int mod=1e9+7;
int a[maxn],t[maxn];
vector<int>vec[maxn];
int dp1[maxn]; //代表走到i位置时拿到了i位置上面的蝴蝶
int dp2[maxn]; //代表走到i位置时没有拿到i位置上面的蝴蝶
int dp3[maxn]; //代表走到i位置时拿到了i位置上面的蝴蝶但是i的儿子的蝴蝶全部没拿到
struct rec{
	int val,id;
	friend bool operator<(const rec&a,const rec&b){
		return a.val>b.val;
	}
};
void dfs(int u,int fa){
	dp1[u]=a[u];	
	dp2[u]=0;
	dp3[u]=a[u];
	int maxx=0;
	int sum=0;
	multiset<rec>s;
	for(int v:vec[u]){
		if(v==fa){
			continue;
		}
		else{
			dfs(v,u);
			dp3[u]+=dp2[v];
			sum+=dp2[v];
			s.insert({dp3[v]-dp2[v],v});
		}
	}
	for(int v:vec[u]){
		if(v==fa){
			continue;
		}
		else{
			if(t[v]==1||t[v]==2){
				//如果直接往我这里走
				maxx=max(maxx,sum-dp2[v]+dp1[v]);
			}
			else{
				//如果直接往我这里走
				maxx=max(maxx,sum-dp2[v]+dp1[v]);
				//如果先走一个地方再往这里走
				if(!s.empty()){
					auto now=*s.begin();
					if(now.id==v){
						s.erase(s.begin());
						if(!s.empty()){
							auto tmp=*s.begin();
							maxx=max(maxx,sum-dp2[v]+dp1[v]+tmp.val);
						}
						s.insert(now);
					}
					else{
						maxx=max(maxx,sum-dp2[v]+dp1[v]+now.val);
					}
				}
			}
		}
	}
	dp1[u]+=maxx;
	dp2[u]+=maxx;
}
void solve(){
	int n;
	cin>>n;
	for(int i=1;i<=n;i++){
		vec[i].clear();
		cin>>a[i];
	}
	for(int i=1;i<=n;i++){
		cin>>t[i];
	}
	for(int i=1;i<=n-1;i++){
		int u,v;
		cin>>u>>v;
		vec[u].push_back(v);
		vec[v].push_back(u);
	}
	dfs(1,0);
	cout<<max({dp1[1],dp2[1],dp3[1]})<<"\n";
}
signed main(){
	io;
	int t=1;
	cin>>t;
	while(t--){
		solve();
	}
}

2021南京站L题
题目描述:
给定一个高为H的二维平面,途中有一些挡板和硬币,拿到硬币可以加分,从 ( 0 , 0 ) (0,0) (0,0) v ⃗ = ( 1 , 1 ) \vec{v}=(1,1) v =(1,1)的初始速度出发,碰到挡板反弹改变 y y y方向,现在可以随意的抽走挡板,问能得到的最高分是多少
在这里插入图片描述
思路:
因为碰到挡板只会改变 y y y方向,所以直线永远都是x+y=k或者和x-y=k的,观察到有高度H的限制,实际上在往右上角运动的时候满足 ( 2 H − y + x )   m o d   2 H = k (2 H-y+x) \bmod 2 H=k (2Hy+x)mod2H=k,在往右下角运动的时候满足 ( x + y )   m o d   2 H = k (x+y) \bmod 2 H=k (x+y)mod2H=k,那么我们的直线轨迹就可以通过 k k k来确定,假设经过这个点的时候方向是右下那么 k = ( x + y )   m o d   2 H k=(x+y) \bmod 2 H k=(x+y)mod2H,假设经过这个点的时候方向是右上,那么 k = ( 2 H − y + x )   m o d   2 H k=(2 H-y+x) \bmod 2 H k=(2Hy+x)mod2H,此外在有挡板的时候如果抽去挡板和不抽去挡板,假设最终方向是右上的,那么这个方向的直线可以来源自不抽去挡板向右下的和抽取挡板向右上的,所以取两者最大值即可,然后 d p dp dp转移

#include<bits/stdc++.h>
#define int long long
#define io ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
using namespace std;
const int maxn=2e5+5;
const int inf=1e9+7;
const int mod=1e9+7;
struct node{
	int x,y;
	int flag=0;
};
int h,n,m;
vector<node>vec;
unordered_map<int,int>dp;
bool compare(node a,node b){
	return a.x<b.x;
}
void calc(){
	int ans=1;
	dp[0]=1;
	for(int i=0;i<n+m;i++){
		int x=vec[i].x,y=vec[i].y,c=vec[i].flag;
		//如果这个地方是硬币
		if(c==1){
			//如果当前是向右下的直线
			if(dp[(x+y)%(2*h)])dp[(x+y)%(2*h)]++;
			//如果当前是向右上的直线
			if(dp[(2*h-y+x)%(2*h)])dp[(2*h-y+x)%(2*h)]++;
		}
		//如果这个地方是挡板
		else if(c==0){
			//那么拿还是不拿
			//对于最后继续向右上走的有下面两种可能取最大值,看是否把挡板拿走
			//  \   /    /
			//   \./    /
			//         /
			//对于最后继续向右下走的同理
			int maxx=max(dp[(x+y)%(2*h)],dp[(2*h-y+x)%(2*h)]);
			dp[(2*h-y+x)%(2*h)]=maxx;
			dp[(x+y)%(2*h)]=maxx;
		}
	}
	for(auto[x,y]:dp){
		ans=max(ans,y);
	}
	cout<<ans-1<<"\n";
}
void solve(){
	dp.clear();
	vec.clear();
	cin>>h>>n;
	for(int i=1;i<=n;i++){
		int x,y;
		cin>>x>>y;
		node tmp;
		tmp.x=x,tmp.y=y,tmp.flag=0;
		vec.push_back(tmp);
	}
	cin>>m;
	for(int i=1;i<=m;i++){
		int x,y;
		cin>>x>>y;
		node tmp;
		tmp.x=x,tmp.y=y,tmp.flag=1;
		vec.push_back(tmp);
	}
	sort(vec.begin(),vec.end(),compare);
	calc();
}
signed main(){
	io;
	int t=1;
	cin>>t;
	while(t--){
		solve();
	}
}

2020秦皇岛站K题
题目描述:
给定n个节点的一颗树,从1号节点可以派兵出发,每次可以移动一格,假设兵从u移动到了v,那么u->v路径上的全部节点都被占领,问占领全部节点需要的最少时间?
思路:
看着像是树形dp,但是因为各个子树之间是互相有影响的所以不好写,实际上可以贪心,我们要到v这个节点,要嘛就是直接从1号节点派兵到v,要嘛就是从另外一棵树的叶子节点出发到v,两者取最小值,可以想到另外一棵树肯定是v的兄弟节点为根的子树,那么转移过后,兄弟节点的深度优势就不存在了,现在这个兵在v的叶子的深度,那么最后往上转移的时候可以发现到了判断u节点和其兄弟的花费的时候,u节点留下的目前还能更新别的兄弟的深度就是u节点的子树的最深深度,所以我们按照最深深度排序,然后兄弟节点之间用最深深度小的去试着更新最深深度大的即可

#include<bits/stdc++.h>
#define int long long
#define io ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
using namespace std;
const int maxn=1e6+5;
const int inf=1e9+7;
const int mod=1e9+7;
vector<int>vec[maxn];
int depth[maxn];
int maxv[maxn];
int ans;
bool cmp(int &p, int &q) {
	return maxv[p]<maxv[q];
}
void dfs1(int u,int fa){
	if(u==1){
		depth[u]=0;
	}
	else{
		depth[u]=depth[fa]+1;
	}
	maxv[u]=0;
	int flag=0;
	for(int v:vec[u]){
		if(v==fa){
			continue;
		}
		else{
			flag=1;
			dfs1(v,u);
			maxv[u]=max(maxv[u],maxv[v]);
		}
	}
	if(!flag){
		maxv[u]=depth[u];
	}
	sort(vec[u].begin(),vec[u].end(),cmp);
}
void dfs2(int u,int fa){
	int minn=0;
	int flag=0;
	for(auto v:vec[u]){
		if(v==fa){
			continue;
		}
		dfs2(v,u);
		if(flag==0){
			flag=1;
			ans+=1;
		}
		else{
			if(minn-depth[u]+1<depth[v]){
				ans+=minn-depth[u]+1;
			}
			else{
				ans+=depth[v];
			}
		}
		minn=maxv[v];
	}
//	cout<<" u 是"<<u<<"\n";
//	cout<<"现在的答案是"<<ans<<"\n";
}
void solve(int i){
	ans=0;
	int n;
	cin>>n;
	for(int i=1;i<=n;i++){
		vec[i].clear();
	}
	for(int i=2;i<=n;i++){
		int u=i;
		int v;
		cin>>v;
		vec[u].push_back(v);
		vec[v].push_back(u);
	}
	dfs1(1,0);
	dfs2(1,0);
	cout<<"Case #"<<i<<": "<<ans<<"\n";
}
signed main(){
	io;
	int t=1;
	cin>>t;
	for(int i=1;i<=t;i++){
		solve(i);
	}
}
/*
1
10
1 2 2 2 4 4 5 8 8*/

2020昆明站C题
题目描述:
给定一个长度为n的序列,每次操作可以把连续的相同数字变为其他数字,问把所有数字变为相同数字需要的最小次数 (n<=5000)
思路:

  • 对于一段区间[l,r],要用最少次数变为相同数字,这个数字一定可以是a[l]或a[r]
  • 如果n个元素互不相同那么需要n-1次操作,否则如果a[l]==a[r]那么可以把a[l+1]~a[r-1]变成a[l]需要n-2次操作,可以减少一次操作

所以本题即为找到左右端点相同数字的区间可以减少答案,区间dp即可,因为这里的n<=5000所以直接区间dp时间复杂度是会炸的,但是题目告诉我们同时拥有x的数字个数不会超过15个,所以我们用pre[r]记录一下前面相同数字的位置转移即可

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int maxn=5e3+5;
const int inf=1e18;
int f[maxn][maxn];
int a[maxn];
int mp[maxn];
int pre[maxn];
void solve(){
	int n;
	cin>>n;
	int m = 0;
	for(int i=1;i<=n;i++){
		cin>>a[i];
		if(a[i]==a[i-1]){
			i--,n--;
		}
		else{
			m++;
		}
	}
	
	for(int i=1;i<=m;i++){
		mp[i]=pre[i]=0;
	}
	for(int i=1;i<=m;i++){
		pre[i]=mp[a[i]];
		mp[a[i]]=i;
	}
	for(int i=1;i<=m;i++){
		for(int j=1;j<=m;j++){
			if(i==j){
				f[i][j]=0;
			}
			else{
				f[i][j]=inf;
			}
		}
	}
	for(int len=2;len<=m;len++){
		for(int l=1;l+len-1<=m;l++){
			int r=l+len-1;
			int&x=f[l][r];
			if(a[l]!=a[r]){
				//变成l颜色或者r颜色
				x=min({x,f[l + 1][r]+1,f[l][r - 1]+1});
			}
			for(int k=pre[r];k>=l;k=pre[k]){
				x=min(x,f[l][k]+f[k+1][r]);
			}
		}
	}
	cout<<f[1][m]<<"\n";
}
signed main(){
	int t;
	cin>>t;
	while(t--){
		solve();
	}
}

2017西安站J题
题意:
给定5个长度为100的01序列,要求每行要选出1个1,并且这些1不能在同一列,有多少种选择方案
思路:
直接dp不好处理,但是观察到 n n n只有5,所以可以直接全排列,然后后面的序列选的1的位置必须在前面的序列选的1的位置的后面,这样的时间复杂度是 O ( 5 ! ∗ 500 ) O(5!*500) O(5!500)的,过题绰绰有余

#include<bits/stdc++.h>
#define int long long
#define io ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
using namespace std;
const int maxn=2e5+5;
const int inf=1e9+7;
const int mod=1e9+7;
string s[6];
int dp[6][105];
void solve(){
	int ans=0;
	for(int i=2;i<=5;i++){
		cin>>s[i];
	}
	for(int i=1;i<=5;i++){
		s[i]=" "+s[i];
	}
	sort(s+1,s+1+5);
	do{
		memset(dp,0,sizeof(dp));
		for(int i=1;i<=5;i++){
			for(int j=1;j<=100;j++){
				dp[i][j]=dp[i][j-1];
				if(s[i][j]=='1'){
					if(i>=2)dp[i][j]+=dp[i-1][j-1];
					else dp[i][j]+=1;
					dp[i][j]%=mod;
				}
			}
		}
		ans+=dp[5][100];
	}while(next_permutation(s+1,s+1+5));
	cout<<ans*531192758%mod<<"\n";
}
signed main(){
	int t=1;

	while(cin>>s[1]){
		solve();
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值