namomo week1

题单

A - Crystalfly

题意

给你一棵树,每个点上有a[i]个蝴蝶,当你走到u点,与u相邻的v的蝴蝶会在t[v]秒之后被惊动逃走(但是v的儿子不会被惊动),问你最多能抓到几只蝴蝶。

思路

可以注意到t数组的范围是1<= t <= 3,我们跟据t把u的儿子分为三类。第一秒可以选三类中的任意一个,第二秒可以选择继续走第一秒选择的儿子,或者是回到u,但是此时第三秒只能走t=3的那个儿子了(一来一回过去了2秒,t小于3的蝴蝶都被惊动飞走了,而往第一秒选择的儿子走显然不是最优)。
这两类行走方案如下
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fCoH09r9-1670481473128)(/upload/2022/02/image-a2f2948ff89345e8a3e1429c4135acb8.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MEPE6XdI-1670481473129)(/upload/2022/02/image-5b2d036624f841cc9af43222cd206d8b.png)]

我们先不管根结点u抓不抓得到
定义

  • dp[i][0]代表i不选并且所有儿子已经不能再抓了的最大值(就是两种路线取最大值)
  • dp[i][1]代表i选并且所有儿子已经不能再抓了的最大值
  • dp[i][2]代表i不选并且所有儿子都不选且已经不能再抓了的最大值
  1. 第一张图
    • 我们选择第一个儿子,其他儿子都不选,所以dp[u][0] = sum{dp[son][0]} 加上a最大的一个儿子
    • dp[u][2]相比于此时的dp[u][0]差别在于少加一个a最大的儿子
  2. 第二张图
    • 我们枚举那些可以二次拯救的点(即t=3的点),假设所有点的贡献都是dp[i][0],就是sum{dp[son][0]},就是dp[u][2],然后拯救的点(第三步走的点)j的a[j]可以加上。对于每个点v变成第一步走的点贡献变化是a[v] + dp[v][2] - dp[v][0],显然我们要选变化最大的点走第一步,但是这个点可能和我们枚举的拯救的点是同一点,所以我们要记录变化前二大的点(判断点是否是同一个点,所以还要记录点的编号)。
    • 由此dp[u][0] = max(dp[u][0],dp[u][2] + a[j] + (c[0].second == j ? c[1].first : c[0].first))

最后dp[u][1] = dp[u][0] + a[u]

代码

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

#define int long long
#define endl '\n'



typedef double db;
typedef long long ll;
typedef unsigned long long ull;

#define debug(args...) { string _s = #args; replace(_s.begin(), _s.end(), ',', ' '); stringstream _ss(_s); istream_iterator<string> _it(_ss); err(_it, args); }

void err(istream_iterator<string> it) {}
template<typename T, typename... Args>
void err(istream_iterator<string> it, T a, Args... args) {
	cerr << *it << " = " << a << endl;
	err(++it, args...);
}
int qmi(int a, int k, int p){int res = 1;while (k){if (k & 1) res = (ll)res * a % p;a = (ll)a * a % p;k >>= 1;}return res;}
int qpow(int a,int b){int res = 1;while(b){if(b&1) res *= a;b>>=1;a*=a;}return res;}
int mo(int x,int p){return x = ((x%p)+p)%p;}
int gcd(int a,int b){return b?gcd(b,a%b):a;}


const int maxn = 1e6+7;
const int mod = 1e9+7;
const double eps = 1e-6;
const int INF = 0x3f3f3f3f3f3f3f3f;
int dx[] = {0,0,1,-1}, dy[] = {1,-1,0,0};

int T = 1,N,M,K,Q;
int e[maxn],w[maxn],ne[maxn],h[maxn],idx;

void add(int a, int b) {
	e[idx] = b, ne[idx] = h[a], h[a] = idx ++;
}
void add(int a, int b, int c) {
	e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++;
}
vector<int> a(maxn),t(maxn);
vector<vector<int>> dp(3,vector<int>(maxn));

void dfs(int u, int fa) {
	
	dp[0][u] = 0;
	int max_son = 0;
	int son_num = 0;
	vector<pair<int, int> > c = {make_pair(-1e18,0ll), make_pair(-1e18,0ll),make_pair(-1e18,0ll)};
	for (int i = h[u]; ~i; i = ne[i]) {
		int j = e[i];
		if (j == fa) continue;
		son_num++;
		dfs(j, u);
		max_son = max(max_son, a[j]);
		dp[0][u] += dp[0][j];
		c[2] = make_pair(a[j] + dp[2][j] - dp[0][j],j);
		sort(c.begin(),c.end(),[&](pair<int, int> aa, pair<int, int> bb) {
			if (aa.first != bb.first) {
				return aa.first > bb.first;
			} else {
				return aa.second < bb.second;
			}
		});
	}
	
	int temp = dp[0][u];
	dp[2][u] = dp[0][u];
	dp[0][u] = dp[0][u] + max_son;//找个最大儿子进,其他县摆烂
	
	// if (son_num > 1) {
	for (int i = h[u]; ~i; i = ne[i]) {
		int j = e[i];
		if (j == fa || t[j] != 3) continue;
		//回首套的点
		dp[0][u] = max(dp[0][u], temp + a[j] + (c[0].second == j ? c[1].first : c[0].first));
	}
	// }
	
	dp[1][u] = dp[0][u] + a[u];
	
}
void solve(){
	
	cin >> N;
	idx = 0;
	memset(h,-1,sizeof(int)*(N+10));


	for (int i = 1; i <= N; i ++) cin >> a[i];
	for (int i = 1; i <= N; i ++) cin >> t[i];
	
	for (int i = 1; i < N; i ++) {
		int u, v;
		cin >> u >> v;
		add(u, v);
		add(v, u);
	}
	
	dfs(1, -1);
	cout << dp[1][1] << endl;


}
signed main()
{
	ios::sync_with_stdio(false);cin.tie(0);
	cin >> T;
	for (int i = 1; i <= T; i ++) solve();
    return (0-0); //<3
} 

B - Towers

题意

给你一棵树,每个点的权值为a[i],现在要求你给每个点的e[i]赋值,使得每个点都存在两个点u,v,这个点在u,v的简单路径上,且e[u]>=a[i]且e[v]>=a[i]。求所有e[i]的和(e[i] >= 0)

思路

我们首先找到权值最大的点root,以它为根。因为根最大,肯定有两个点的e是a[root]是专门用来满足root的。这样对于除了根以外的所有点i都可以从上述两个点其中一个到根再到i,这样来获得u,v的其中一个。而另一个一定是在i的子树当中(包括i本身)。如果子树的最大值大于等于a[i]了,那么e[i]=0,不需要额外开销;如果子树的最大值小于a[i],我们则需要把最大值补成a[i],开销是a[i]-max。

因为要先知道子树中的最大值,所以我们先递归到叶子结点,在回溯时从下往上处理。

当回溯到根的时候,因为我们一开始是设了两个点的e等于a[root]的,他们应该在root不同的儿子里,要不然就不是简单路径了。所以我们把root的每个儿子的子树的最大值存起来,取前两个大的补成a[root]。

但是root可能只有一个儿子,那么我们只需要把e[root]设为a[root]

代码

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


#define INF 0x3f3f3f3f3f3f3f3f
#define int long long
#define endl "\n"


#define error(args...) { string _s = #args; replace(_s.begin(), _s.end(), ',', ' '); stringstream _ss(_s); istream_iterator<string> _it(_ss); err(_it, args); }

void err(istream_iterator<string> it) {}
template<typename T, typename... Args>
void err(istream_iterator<string> it, T a, Args... args) {
	cerr << *it << " = " << a << endl;
	err(++it, args...);
}
typedef double db;
typedef long long ll;
typedef unsigned long long ull;

int qmi(int a, int k, int p){int res = 1;while (k){if (k & 1) res = (ll)res * a % p;a = (ll)a * a % p;k >>= 1;}return res;}
int qpow(int a,int b){int res = 1;while(b){if(b&1) res *= a;b>>=1;a*=a;}return res;}
int mo(int x,int p){return x = ((x%p)+p)%p;}
int gcd(int a,int b){return b?gcd(b,a%b):a;}


const int maxn = 1e6+7;
const int mod = 1e9+7;
const double eps = 1e-6;
int dx[] = {0,0,1,-1}, dy[] = {1,-1,0,0};

int T = 1,N,M,K,Q;

int e[maxn],w[maxn],ne[maxn],h[maxn],idx;

void add(int a, int b) {
	e[idx] = b, ne[idx] = h[a], h[a] = idx ++;
}
void add(int a, int b, int c) {
	e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++;
}

int ans = 0;
int root = 1;
vector<int> a(maxn);
int dfs(int u, int fa) {
	int mx = 0;
	vector<int> mmax;
	for (int i = h[u]; ~i; i = ne[i]) {
		int j = e[i];
		if (j == fa) continue;
		int t = dfs(j, u);
		mx = max(mx, t);
		if (u == root) {
			mmax.push_back(-t);
		}
	}
	if (u == root) {
		sort(mmax.begin(),mmax.end());
		ans += max(0ll,a[root] + mmax[0]);
		if (mmax.size() < 2) {
			ans += a[root];
		}
		else ans += max(0ll,a[root] + mmax[1]);
		return 0;
	}
	if (mx < a[u]) {
		ans += a[u] - mx;
		mx = a[u];
	}
	
	return mx;
}
void solve(){
	cin >> N;
	memset(h,-1,sizeof(int)*(N+10));
	for (int i = 1; i <= N; i ++) cin >> a[i];
		
	for (int i = 1; i < N; i ++) {
		int u, v;
		cin >> u >> v;
		add(u, v);
		add(v, u);
	}

	
	for (int i = 2; i <= N; i ++) {
		if (a[i] > a[root]) {
			root = i;
		}
	}
	
	dfs(root,-1);	
	cout << ans << endl;

}
signed main()
{
	ios::sync_with_stdio(false);cin.tie(0);
	
	// cin >> T;
	for (int i = 1; i <= T; i ++) solve();
    return (0-0); //<3
} 

C - Werewolves

题意

给你一棵树,每个点都有一个颜色,问你某种颜色数量严格大于联通块大小一半的联通块数量。

思路

  • 数量严格大于大小的一半,说明每个合法的联通块都对应着一个颜色,那么我们可以枚举颜色,把每种颜色的合法联通块累加起来就是总的答案。
  • 对于一个颜色color,我们把颜色是color的权值设为1,不同的设为-1,这样就把问题转化为总的权值大于0的联通块个数,就是一个树上背包问题。
  • 普通的树上背包复杂度是O(N2),再加上颜色数最后是O(N3)。但是有些状态我们是不需要枚举的,就是权值和的范围最大是[-cnt[color],cnt[color]],这样复杂度分摊是O(N2)
  • 权值有负的,我们可以加上一个偏移值base

代码

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


#define INF 0x3f3f3f3f3f3f3f3f
#define int long long
#define endl "\n"


#define error(args...) { string _s = #args; replace(_s.begin(), _s.end(), ',', ' '); stringstream _ss(_s); istream_iterator<string> _it(_ss); err(_it, args); }

void err(istream_iterator<string> it) {}
template<typename T, typename... Args>
void err(istream_iterator<string> it, T a, Args... args) {
	cerr << *it << " = " << a << endl;
	err(++it, args...);
}

typedef double db;
typedef long long ll;
typedef unsigned long long ull;

int qmi(int a, int k, int p){int res = 1;while (k){if (k & 1) res = (ll)res * a % p;a = (ll)a * a % p;k >>= 1;}return res;}
int qpow(int a,int b){int res = 1;while(b){if(b&1) res *= a;b>>=1;a*=a;}return res;}
int mo(int x,int p){return x = ((x%p)+p)%p;}
int gcd(int a,int b){return b?gcd(b,a%b):a;}


const int maxn = 1e6+7;
const int mod = 998244353;
const double eps = 1e-6;
int dx[] = {0,0,1,-1}, dy[] = {1,-1,0,0};


int T = 1,N,M,K,Q;

vector<int> G[3030];
void add(int a, int b) {
	G[a].push_back(b);
}


vector<vector<int>> dp(3010,vector<int>(6010));
vector<int> a(3030), siz(3007), f(6060);
vector<int> num(3030);


int color;
int ans = 0;
int base = 3005;
void dfs(int u, int fa) {
	siz[u] = 1;
	if (color == a[u]) dp[u][base+1] = 1;
	else dp[u][base - 1] = 1;
	
	
	for (int j : G[u]) {
		if (j == fa) continue;
		dfs(j, u);
		int bro1 = min(siz[u], num[color]);//之前枚举过的子树
		int bro2 = min(siz[j], num[color]);//当前子树
		for (int x = -bro1; x <= bro1; x ++) {
			f[x + base] = dp[u][x + base];
		}
		//当前子树和之前的子树配对(方案相乘)
		for (int x = -bro1; x <= bro1; x ++) {
			for (int y = -bro2; y <= bro2; y ++) {
				(dp[u][x + y + base] += dp[j][y + base] * f[x + base]) %= mod;
			}
		}
		
		siz[u] += siz[j];
	}
	
	for (int i = 1; i <= num[color]; i ++) {//累加颜色为color,祖先是u的合法联通块
		(ans += dp[u][i + base]) %= mod;
	}
}
void solve(){
	
	
	cin >> N;
	
	for (int i = 1; i <= N; i ++) cin >> a[i], num[a[i]] ++;
		
	for (int i = 1; i < N; i ++) {
		int u, v;
		cin >> u >> v;
		add(u, v);
		add(v, u);
	}
	
	for (int i = 1; i <= N; i ++) {
		if (num[i] == 0) continue;
		//清空初始化
		for (int u = 1; u <= N; u ++ ) 
			for (int j = -num[i]; j <= num[i]; j ++)
            	dp[u][j + base] = 0;
		color = i;
		dfs(1, -1);

	}	

	cout << ans << endl;

}
signed main()
{
	ios::sync_with_stdio(false);cin.tie(0);
	
	// cin >> T;
	for (int i = 1; i <= T; i ++) solve();
    return (0-0); //<3
} 

D - Paint

题意

给你N个数,每次你能选择数值相同的一段把它变成另外一个数值,问把这N个数全部变成一个颜色需要多少次操作。

思路

首先我们把原数组相邻的相同元素进行压缩,压缩后有M个元素

那么我们最多只需要M-1次操作之后所有元素值相同

对于abc当a=c(两端相等)时我们可以减少一次操作

我们定义dp[i][j]代表区间[i,j]最多可以减少几次操作,那么最后答案就是M-1-dp[1][M]

转移方程dp[i][j] = dp[i][j-1],dp[i][j] = max(dp[i][j], dp[i][k]+dp[k+1][j-1]+1),k满足k>=i,且a[k]==a[j].对于k直接枚举区间[i,j]会超时,题目保证每个数最多出现20次,我们只需要预处理出上一个等于a[i]的位置的pre[i]就能保证k最多枚举20次,复杂度为20NN。

代码

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


#define INF 0x3f3f3f3f3f3f3f3f
#define int long long
#define endl "\n"


#define error(args...) { string _s = #args; replace(_s.begin(), _s.end(), ',', ' '); stringstream _ss(_s); istream_iterator<string> _it(_ss); err(_it, args); }

void err(istream_iterator<string> it) {}
template<typename T, typename... Args>
void err(istream_iterator<string> it, T a, Args... args) {
	cerr << *it << " = " << a << endl;
	err(++it, args...);
}
typedef double db;
typedef long long ll;
typedef unsigned long long ull;

int qmi(int a, int k, int p){int res = 1;while (k){if (k & 1) res = (ll)res * a % p;a = (ll)a * a % p;k >>= 1;}return res;}
int qpow(int a,int b){int res = 1;while(b){if(b&1) res *= a;b>>=1;a*=a;}return res;}
int mo(int x,int p){return x = ((x%p)+p)%p;}
int gcd(int a,int b){return b?gcd(b,a%b):a;}


const int maxn = 1e6+7;
const int mod = 1e9+7;
const double eps = 1e-6;
int dx[] = {0,0,1,-1}, dy[] = {1,-1,0,0};

int T = 1,N,M,K,Q;



void solve(){
	
	cin >> N;
	
	vector<int> a(N + 1),nxt(N+1), b;
	b.push_back(0);
	for (int i = 1; i <= N; i ++) {
		cin >> a[i];
		if (i > 1 && a[i] != a[i-1]) {
			b.push_back(a[i-1]);
		}
	}
	b.push_back(a[N]);
	M = b.size() - 1;
	
	
	vector<int> pre(N+10), st(N+10);
	for (int i = 1; i <= M; i ++) {
		if (!st[b[i]]) {
			pre[i] = 0;
			st[b[i]] = i;
		} else {
			pre[i] = st[b[i]];
			st[b[i]] = i;
		}
	}
	vector<vector<int>>dp(N+1,vector<int>(N+1));
	
	for (int len = 2; len <= M; len ++) {
		for (int i = 1; i + len - 1 <= M; i ++) {
			int j = i + len - 1;
			if (len == 2) {
				if (b[i] == b[j]) dp[i][j] = 1;
				else dp[i][j] = 0;
			} else {
				dp[i][j] = dp[i][j-1];
				
				int x = pre[j];
				while (x >= i) {
					dp[i][j] = max(dp[i][j], dp[i][x] + dp[x + 1][j - 1] + 1);
					x = pre[x];
				}
				
			}
		}
	}
	cout << M - 1 - dp[1][M] << endl;
	
	


}
signed main()
{
	ios::sync_with_stdio(false);cin.tie(0);
	cin >> T;
	for (int i = 1; i <= T; i ++) solve();
    return (0-0); //<3
} 

E - Outer space invaders

思路

有N个敌人,每个人出现的时间是a[i]到b[i],出现的位置在d[i],你只能在原点放置炸弹,威力为r的炸弹需要r花费,能炸到[-r,r],问消灭所有敌人的最小花费。

题解

对于每个敌人我们可以表示成一个平行于x轴的线段,高度为d,这样题目就变成了最少需要多长的竖直线段,能穿过所有线段。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Pb4P4i6j-1670481473130)(/upload/2022/03/image-1a16ab2bb9ff440b8f87d5a6bcb0df14.png)]

  • 首先,对于最高的线段,必然有一条红线等于它的高度来消灭它,在消灭它的同时可以借机消灭其他较矮的线段。
  • dp[i][j]代表消灭区间[i,j]内的所有敌人所需要最小花费。
  • 我们找到当前区间的d最大的线段,然后枚举线段上的每一点i,把区间分为了两部分[l,i-1]和[i+1,r]。
  • 如果线段不是真包涵于当前区间,说明它被某个区间的分界线隔断了,而区间的分界线就是有炸弹的时刻。
  • dp[l][r] = min(dp[l][i-1] + dp[i+1][r]) + d[i]

代码

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


#define INF 0x3f3f3f3f3f3f3f3f
#define int long long
#define endl "\n"


#define error(args...) { string _s = #args; replace(_s.begin(), _s.end(), ',', ' '); stringstream _ss(_s); istream_iterator<string> _it(_ss); err(_it, args); }

void err(istream_iterator<string> it) {}
template<typename T, typename... Args>
void err(istream_iterator<string> it, T a, Args... args) {
	cerr << *it << " = " << a << endl;
	err(++it, args...);
}
typedef double db;
typedef long long ll;
typedef unsigned long long ull;

int qmi(int a, int k, int p){int res = 1;while (k){if (k & 1) res = (ll)res * a % p;a = (ll)a * a % p;k >>= 1;}return res;}
int qpow(int a,int b){int res = 1;while(b){if(b&1) res *= a;b>>=1;a*=a;}return res;}
int mo(int x,int p){return x = ((x%p)+p)%p;}
int gcd(int a,int b){return b?gcd(b,a%b):a;}


const int maxn = 1e6+7;
const int mod = 1e9+7;
const double eps = 1e-6;
int dx[] = {0,0,1,-1}, dy[] = {1,-1,0,0};

int T = 1,N,M,K,Q;

vector<int> a(maxn), b(maxn), d(maxn);
vector<vector<int>> dp(1010,vector<int>(1010));
int dfs(int l, int r) {
	if (l > r) return 0;
	if (dp[l][r] != -1) return dp[l][r];
	int &ans = dp[l][r];
	ans = INF;
	pair<int,int> mx = {-1,-1};
	for (int i = 1; i <= N; i ++) {
		if (a[i] >= l && b[i] <= r) {
			if (d[i] > mx.first) {
				mx = {d[i], i};
			}
		}
	}
	
	if (mx.first == -1) return ans = 0;
	
	for (int i = a[mx.second]; i <= b[mx.second]; i ++) {
		ans = min(ans, dfs(l,i-1) + dfs(i+1,r));
	}
	ans += mx.first;
	return ans;
}
void solve(){
	
	cin >> N;
	vector<int> v;	
	for (int i = 1; i <= N; i ++) {
		cin >> a[i] >> b[i] >> d[i];
		v.push_back(a[i]);
		v.push_back(b[i]);
	}
	
	sort(v.begin(),v.end());
	v.erase(unique(v.begin(),v.end()),v.end());
	M = v.size();
	
	
	for (int i = 1; i <= N; i ++) {
		a[i] = lower_bound(v.begin(),v.end(),a[i]) - v.begin() + 1;
		b[i] = lower_bound(v.begin(),v.end(),b[i]) - v.begin() + 1;
	}
	
	
	
	for (int i = 1; i <= M; i ++) 
		for (int j = 1; j <= M; j ++)
			dp[i][j] = -1;
	
	
	
	cout << dfs(1,M) << endl;
	


}
signed main()
{
	cin >> T;
	for (int i = 1; i <= T; i ++) solve();
    return (0-0); //<3
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值