首页 > 其他分享 >23.0101 winter training.2

23.0101 winter training.2

时间:2023-01-01 22:55:34浏览次数:70  
标签:include const winter training.2 ll 23.0101 int define dis

A - Basic Diplomacy

题意

某人有n个朋友 要出去m天,第i天可以选着\(a_i\)个朋友中的一个一起出去玩,每个朋友被选择的次数不能超过\(\lceil m \rceil\)
问是否存在一种方案 能合理的选择每天的朋友

思路

贪心 先把m天按能选择的朋友数量从小到大排列
因为朋友少的天数 选择的余地小 必须先安排更优
然后我们依次安排每一天 我们记录每个朋友被选了几次 每天给安排当天能选的朋友中被选次数更小的那个 然后判断一下每个朋友被选的次数是否大于了限制
用一个优先队列即可实现

#include <bits/stdc++.h>
#include <stdlib.h>
using namespace std;
#define IOS ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
#define ll long long
#define int ll
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const int N = 1e5 + 5;
const int M = 5e5 + 5;
const ll mod = 1e9 + 7;

vector<int>ve[N];
int n, m, val[N], ans[N];
void solve()
{
	cin >> n >> m;

	for(int i = 1; i <= m; i++){
		ans[i] = 0;
		ve[i].clear();
	}
	for(int i = 1; i <= n; i++)
		val[i] = 0;

	priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>>q;
	for(int i = 1, cnt; i <= m; i++){
		cin >> cnt;
		q.push({cnt, i});
		for(int j = 1, x; j <= cnt; j++){
			cin >> x;
			ve[i].push_back(x);
		}
	}

	while(q.size()){
		auto [cnt, now] = q.top();
		q.pop();
		int id = 0, mi = inf;
		for(auto x : ve[now]){
			if(val[x] < mi){
				id = x;
				mi = val[x];
			}
		}
		val[id]++;
		ans[now] = id;
	}

	int num = m / 2;
	if(m % 2) num++;
	for(int i = 1; i <= n; i++){
		if(val[i] > num){
			cout << "NO\n";
			return;
		}
	}
	cout << "YES\n";
	for(int i = 1; i <= m; i++)
		cout << ans[i] << " \n"[i == m];
}


signed main()
{
	IOS;
	int t = 1;
	cin >> t;
	while (t--)
	{
		solve();
	}
}

B - Playlist

题意

给定长度为n的数组
从头开始比较相邻两个数 如果两个数的gcd是1,就删掉后面的那个数 然后不管前面那个数继续往后比较,如果gcd不为1,那就让后面的那个数继续向后比较
最后那个数的后面是前面第一个数 就这样循环比较 删除 直到不能再删位置
求删了多少个数 以及删的顺序

思路

如果直接放进队列中暴力模拟是肯定会tle的 因为可能要比较很多轮才能结束
只有gcd是1我们才会有删的操作
那么我们可以记录每个数后面的数是哪一个用to[]数组记录 然后将gcd为1的x放进队列 删去x后面的那个数并更新to[x]
每次从队列中取出一个数 进行判断或删操作 如果当先取出的数已被删除就继续
这样保证了队列中都是上回gcd为1的第一个数 都是有用的 只有这样的数才能继续往后删另外的数 这样就保证了复杂度

#include <bits/stdc++.h>
#include <stdlib.h>
using namespace std;
#define IOS ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
#define ll long long
#define int ll
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const int N = 2e5 + 5;
const int M = 5e5 + 5;
const ll mod = 998244353;

int n, m, a[N], vis[N], to[N];

void solve()
{
	cin >> n;
	for(int i = 1; i <= n; i++) vis[i] = 0;

	queue<int>q;
	vector<int>ans;
	for(int i = 1; i <= n; i++) {
		cin >> a[i];
		if(i < n) to[i] = i + 1; 
	}
	to[n] = 1;

	if(n == 1 && a[1] == 1){
		cout << 1 << " " << 1 << "\n";
		return;
	}

	for(int i = 1; i <= n; i++){
		if(vis[i]) continue;
		if(gcd(a[i], a[to[i]]) == 1) {
			q.push(i); 
			if(!vis[to[i]]) ans.push_back(to[i]);
			vis[to[i]] = 1;
			to[i] = to[to[i]];
		}
	}
	//cerr << q.size() << "\n";

	while(q.size()){
		auto id = q.front();
		q.pop();
		if(vis[id]) continue;

		if(gcd(a[id], a[to[id]]) == 1){
			if(!vis[to[id]]) ans.push_back(to[id]);
			vis[to[id]] = 1;
			to[id] = to[to[id]];
			q.push(id);
		}
	}

	cout << ans.size() << " ";
	for(auto x : ans) cout << x << " ";
	cout << "\n";
}


signed main()
{
	IOS;
	int t = 1;
	cin >> t;
	while (t--)
	{
		solve();
	}
}

D - Useful Edges

https://codeforces.com/problemset/problem/1484/F

题意

给你n个点m条边的有权无向图
在给定q个三元组 (u,v,l)
一条边e[x,y]被认为是好的 如果存在至少一个三元组 (u,v,l)满足 u到v存在一条包含边e[x, y]的路径且这条路径的权值不大于l
根据题意我们可以转换一个公式:\(dis[u][x] + dis[y][v] + e[x][y] <= l\)其中dis[i][j]代表i到j的最短路(用floyd求即可)
我们再化简公式:\(dis[u][x] + e[x][y] <= l - dis[y][v]\)
我们先把左边那部分看成一个整体 即u到y的路径权值 如果要满足三元组(u,v,l)必须小于等于\(l - dis[y][v]\)
因为只要满足一个就能成为好边 那我们不妨让u到y的路径权值为最大 即取max(l - dis[y][v]), 我们把这个值当做u到y的路径最大可以是多少 即mxdis[u][y]
然后我们再去判断每条边是不是好边
如果存在某个包含e[x,y]的路径 使得\(dis[u][x(y)] + e[x,y] <= mxdis[u][y(x)]\) 说明这个边就是好边
左边dis[u][x(y)]确保了u到x足够小, 右边的mxdis[u][y]则是要满足至少一个三元组u到y的最大值
如果某个起点到e的第一个结点的权值加上该边权比该起点到e的第二个结点的最大可取值(mxdis)小 不就说明e是好边吗

#include <bits/stdc++.h>
#include <stdlib.h>
using namespace std;
#define IOS ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
#define ll long long
#define int ll
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const int N = 2e5 + 5;
const int M = 5e5 + 5;
const ll mod = 998244353;

int n, m;
int dis[605][605], mx[605][605], is[605 * 605];
pair<pair<int, int>, int>e[605 * 605];

void solve()
{
	cin >> n >> m;

	for(int i = 1; i <= n; i++){
		for(int j = 1; j <= n; j++){
			if(i == j) dis[i][j] = 0;
			else dis[i][j] = INF;
			mx[i][j] = -inf;
		}
	}
	for(int i = 1; i <= m; i++) is[i] = 0;

	for(int i = 1, u, v, w; i <= m; i++){
		cin >> u >> v >> w;
		dis[u][v] = dis[v][u] = w;
		e[i] = {{u, v}, w};
	}
		
	for(int k = 1; k <= n; k++)
		for(int i = 1; i <= n; i++)
			for(int j = 1; j <= n; j++)
				dis[i][j] = min(dis[i][j], dis[i][k] + dis[k][j]);
		
	
	int q;
	cin >> q;
	for(int i = 1; i <= q; i++){
		int u, v, l;
		cin >> u >> v >> l;
		for(int j = 1; j <= n; j++){
			mx[u][j] = max(mx[u][j], l - dis[j][v]);
			mx[v][j] = max(mx[v][j], l - dis[j][u]);
		}
	}

	for(int i = 1; i <= m; i++){
		int u = e[i].first.first;
		int v = e[i].first.second;
		int w = e[i].second;
		for(int j = 1; j <= n; j++){
			if(dis[j][u] + w <= mx[j][v] || dis[j][v] + w <= mx[j][u]){
				is[i] = 1;
				break;
			}
		}
	}

	int ans = 0;
	for(int i = 1; i <= m; i++) ans += is[i];
	cout << ans << "\n";
}


signed main()
{
	IOS;
	int t = 1;
	//cin >> t;
	while (t--)
	{
		solve();
	}
}

E - Between Two Arrays

题意

给定长度为n的非递减数组a[],b[]
且保证a[i]<=b[i] 要求构造一个长度为n的非递减数组c[],满足\(a[i]\geq c[i] \leqb[i]\)
求方案数

思路

dp
dp[i][j]代表 第i个数为j前面有多少种方案
我们可以将每组[a[i],b[i]]当做一个区间 即就是求每个c[i]在对应区间内且要非递减
因为要满足非递减 第i-1个数必须小于等于第i个数 那么前面一段的左边界到当前j的方案数和就是第i个数取值为j的方案数(这个可以用前缀维护)
转换为dp方程如下
\(dp[i][j] = pre[i - 1][j] - pre[i - 1][a[i-1] - 1]\)
还有就是要注意当前段和前一段没有重合的情况 这种情况每个取值都是前一段整段方案数累加和
即 \(dp[i][j] = pre[i - 1][b[i]] - pre[i - 1][a[i - 1] - 1]\)

#include <bits/stdc++.h>
#include <stdlib.h>
using namespace std;
#define IOS ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
#define ll long long
#define int ll
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const int N = 3e3 + 5;
const int M = 5e5 + 5;
const ll mod = 998244353;

int n, a[N], b[N];
int dp[N][N];
int pre[N][N];
void solve()
{
	cin >> n;

	for(int i = 1; i <= n; i++) cin >> a[i];
	for(int j = 1; j <= n; j++) cin >> b[j];

	for(int i = a[1]; i <= b[1]; i++) {
		dp[1][i] = 1;
		pre[1][i] = (pre[1][i - 1] + dp[1][i]) % mod;
	}

	for(int i = 2; i <= n; i++){
		for(int j = a[i]; j <= b[i]; j++){
			if(j >= b[i - 1]) dp[i][j] = (pre[i - 1][b[i - 1]] - pre[i - 1][a[i - 1] - 1] + mod) % mod;
			else dp[i][j] = (pre[i - 1][j] - pre[i - 1][a[i - 1] - 1] + mod) % mod;
		}

		for(int j = a[i]; j <= b[i]; j++){
			pre[i][j] = (pre[i][j - 1] + dp[i][j]) % mod;
		}
	}

	cout << pre[n][b[n]] << "\n";
}


signed main()
{
	IOS;
	int t = 1;
	//cin >> t;
	while (t--)
	{
		solve();
	}
}

F - Red and Blue Tree

https://atcoder.jp/contests/abc222/tasks/abc222_e?lang=en

题意

给定一个n个节点的树 然后给定几条路径 让你给每条边涂色红或蓝 再给你红色边经过次数和蓝色边经过次数的差k 问你有多少种涂色方案

思路

我们可以用差分 求得每条边被经过了几次
具体就是 先让路径两端点权值+1 他们的lca权值-2
最后dfs一遍 最后每个点的权值就是它们连向父亲的边的经过次数
最后根据差k计算 红色要多少次 (特判非法情况)
然后用背包求最后的方案数 注意第一个点不算进去 根节点没有父亲

#include <bits/stdc++.h>
#include <stdlib.h>
using namespace std;
#define IOS ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
#define ll long long
#define int ll
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const int N = 3e3 + 5;
const int M = 5e5 + 5;
const ll mod = 998244353;

int n, m, k, ans, a[N];
vector<ll> g[N];
ll fa[N][32], dep[N], val[N];

void dfs(ll x, ll pre, ll d)//找到每个节点父亲与深度
{
	fa[x][0] = pre;
	dep[x] = d;
	for (auto to : g[x])
		if (to != pre)
			dfs(to, x, d + 1);
}

ll LCA(ll u, ll v)
{
	if (dep[u] > dep[v])
		swap(u, v);
	ll temp = dep[v] - dep[u];
	for (ll i = 0; ((1ll << i) <= temp); i++)//将u v移到同一深度
	{
		if ((1ll << i) & temp)
			v = fa[v][i];
	}
	if (u == v)
		return u;
	for (ll i = log2(n); i >= 0; i--)
	{							
		if (fa[u][i] != fa[v][i])
		{
			u = fa[u][i];
			v = fa[v][i];
		}
	}
	return fa[u][0];
}

void init()
{
	dfs(1, -1, 0);
	for (ll j = 0; (1ll << j) <= n; j++)//预处理出每个节点往上走2^j所到的节点,超过根节点记为-1
	{
		for (ll i = 1; i <= n; i++)
		{
			if (fa[i][j] < 0)
				fa[i][j + 1] = -1;
			else
				fa[i][j + 1] = fa[fa[i][j]][j];
		}
	}
}

void dfs2(int x, int fa){
	for(auto to : g[x]){
		if(to == fa) continue;
		dfs2(to, x);
		ans += val[to];
		val[x] += val[to];
	}

}

int dp[1000005];

void solve()
{
	cin >> n >> m >> k;
	
	for(int i = 1; i <= m; i++)
		cin >> a[i];
	
	for(int i = 1, u, v; i < n; i++){
		cin >> u >> v;
		g[u].push_back(v);
		g[v].push_back(u);
	}
	init();
	for(int i = 2; i <= m; i++){
		int lc = LCA(a[i - 1], a[i]);
		//cerr << "lc: " << lc << "\n";
		val[lc] -= 2;
		val[a[i - 1]]++;
		val[a[i]]++;
	}
	//cerr << k << "\n";
	// for(int i = 1; i <= n; i++) 
	// 	cerr << val[i] << " \n"[i == n];

	ans = 0;
	dfs2(1, -1);

	k = abs(k);
	if(ans < k || (ans - k) % 2) {
		cout << 0 << "\n";
		return;
	}

	k = (ans - k) / 2;
	// cerr << k << "\n";
	// for(int i = 1; i <= n; i++) 
	// 	cerr << val[i] << " \n"[i == n];
	dp[0] = 1;
	int cnt = 0;
	for(int i = 2; i <= n; i++){
		if(val[i] == 0) {
			cnt++;
			continue;
		}
		for(int j = k; j >= val[i]; j--)
			dp[j] = (dp[j] +  dp[j - val[i]]) % mod;
	}
	// for(int i = 0; i <= k; i++)
	// 	cerr << dp[i] << " \n"[i == k];
	int num = 1;
	for(int i = 1; i <= cnt; i++){
		num = num * 2 % mod;
	}
	cout << dp[k] * num % mod << "\n";
}


signed main()
{
	IOS;
	int t = 1;
	//cin >> t;
	while (t--)
	{
		solve();
	}
}

G - Expensive Expense

https://atcoder.jp/contests/abc222/tasks/abc222_f?lang=en

题意

给定一棵树 每个节点和边都有点权 求每个点i 到所有点的最大花费
i到j的花费是 i到j的路径经过边权和再加上j的点权

思路

对于每个点权a[i] 其实我们可以给对应点i和虚点i+n 连上一条a[i]的边
然后在这课树上找到直径(u, v + n) 这个直径意义有一点不同 它指的是某个点到某个虚点的最长路
然后我们可以类比直径的性质
从u到v的路径上的某个点出发 肯定最后走到u或v这两个点中的一个点最优
证明:
对于在我们所谓的直径上的点x: x到除u + n, v + n以外的虚点肯定不是最长的
如果dis[x, y + n]是最长的 那么直径就是u -> y,即dis[x, v + n]肯定大于dis[x, y + n]
对于不在我们所谓直径上的点x: dis[x, y + n]也是小于dis[x, v + n]的
这个x肯定能到达直径上的某个点c, dis[x, v + n] = dis[x, c] + dis[c, v + n], dis[x, y + n] = dis[c, y + n] - dis[c, x]
显然 dis[x, c] + dis[c, v + n] >= dis[x, y + n] = dis[c, y + n] - dis[c, x]
即dis[x, v + n] >= dis[x, y + n]
最后还要考虑一下 u到它的虚点的值可能很大 故最后到u, v要取优
然后还要考虑一点自己不能到到自己 如果到直径的某个点的距离是它的点权 那它就要去另外一端点才是答案

求直径的时候不要直接和虚点一起在图上求 会很乱 只要每次dfs中比较时加上点权就好了

#include <bits/stdc++.h>
#include <stdlib.h>
using namespace std;
#define IOS ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
#define ll long long
#define int ll
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const int N = 1e6 + 5;
const int M = 5e5 + 5;
const ll mod = 998244353;

int n, d[N], dis[N], dis2[N], mx, mxid, st, ed, di;
vector<pair<int, int>>g[N];

void dfs(int x, int fa){
	for(auto [to, w] : g[x]){
		if(to == fa) continue;
		dis[to] = max(dis[to], dis[x] + w);
		if(dis[to] + d[to] > mx) {
			mx = dis[to] + d[to];
			mxid = to;
		}

		dfs(to, x);
	}
}

void cal(int s){
	mx = 0;
	for(int i = 1; i <= n * 2; i++) dis[i] = 0;
	dfs(s, -1);
}

void get_dom(int s){
	cal(s);
	int pos1 = mxid;

	cal(pos1);
	int pos2 = mxid;

	for(int i = 1; i <= n * 2; i++) 
		dis[i] = 0, dis2[i] = 0;
	
	cal(pos1);
	for(int i = 1; i <= n * 2; i++){
		dis2[i] = dis[i] + d[pos1];
		dis[i] = 0;
	}
	cal(pos2);
	for(int i = 1; i <= n * 2; i++)
		dis[i] += d[pos2];
}

void solve()
{
	cin >> n;
	for(int i = 1, u, v, w; i < n; i++){
		cin >> u >> v >> w;
		g[u].push_back({v, w});
		g[v].push_back({u, w});
	}

	for(int i = 1; i <= n; i++)
		cin >> d[i];

	get_dom(1);

	for(int i = 1; i <= n; i++){
		if(dis[i] == d[i]) cout << dis2[i] << "\n";
		else if(dis2[i] == d[i]) cout << dis[i] << "\n";
		else cout << max(dis[i], dis2[i]) << "\n";
	}
}


signed main()
{
	IOS;
	int t = 1;
	//cin >> t;
	while (t--)
	{
		solve();
	}
}

标签:include,const,winter,training.2,ll,23.0101,int,define,dis
From: https://www.cnblogs.com/yaqu-qxyq/p/17019202.html

相关文章