23.0101 winter training.2
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();
}
}