一.E - Add and Mex (atcoder.jp)
(1)题目大意
给定你一串数,M次操作,每一次操作对于a[i]这个元素都会加上i,现在问你第i次操作后,该数组的最小非负整数补集是多少?
(2)解题思路
对于n来说,有2*10^5,不能够直接模拟,那么我们发现一些性质,对于第i次操作,如果他加上i后大于n了,那么这次操作和这次操作后的都属于无效操作。对于每一个元素来说,我们可以知道他要进行多少次操作才能把这个数变为非负数,对于这个操作次数之前的操作次数,都不会影响答案,因此我们对于每个元素处理就是次数小于等于m,并且+i小于等于n才会对答案有贡献。时间复杂度(nlogn)。
(3)代码实现
#include "bits/stdc++.h"
using namespace std;
const int N = 2e5 + 10;
vector <int> num[N];
int a[N];
void solve()
{
int n,m;
cin >> n >> m;
for(int i = 1;i <= n;i++) {
cin >> a[i];
int k = 1;
if(a[i] < 0) k = (-a[i] % i ? -a[i] / i + 1 : -a[i] / i);
else k = 1;
while(k <= m && a[i] + i * k <= n) {
num[k].push_back(a[i] + i * k);
k ++;
}
}
for(int i = 1;i <= m;i++) {
sort(num[i].begin(),num[i].end());
num[i].erase(unique(num[i].begin(),num[i].end()),num[i].end());
int mex = 0;
for(auto x:num[i])
if(x == mex) mex ++;
else break;
cout << mex << endl;
}
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
int T;
T = 1;
while(T --) {
solve();
}
return 0;
}
(1)题目大意
给定你n堆石头,每一堆石头最多有10^12个,Aoki 先手,Takahashi 后手,现在问你在你能把第一堆石子不能为空的情况下,可以把第一堆石子移动到第二堆,能否让Takahashi 胜利。
(2)解题思路
这可以看出来是个NIM游戏的变种,也就是说我们可以认为的操作第一堆和第二堆,看能否导致游戏局面发生变化,由NIM博弈可知,若n堆石子异或为0,则先手必败,因此Takahashi 胜利等价于Aoki必败,对于后面n-2堆石子是不可变状态,把后面n-2堆异或出来后,看能否把第一堆石子和第二堆石子的异或变成后面n-2堆异或的值。
设后面n-2堆石子异或出来的值为x,前面2堆和为s,设d = (s - x) / 2(表示至少会剩下么多石子)
情况1,若x > s,则不可能得到。
情况2,若x和s奇偶性不同,也不可能得到。
情况3,若d > 第一堆或者(d & x)不为0,也不可能得到
情况4,加上额外要去掉的就是x在第i位有的,并且d在第i位没有的,并且还要保证加上小于等于第一堆。
情况5,若最后剩下的an为0,则答案不可能得到。
(3)代码实现
#include "bits/stdc++.h"
using namespace std;
const int N = 310;
long long a[N];
void solve()
{
int n;
cin >> n;
long long x = 0,s = 0;
for(int i = 1;i <= n;i++) {
cin >> a[i];
if(i > 2) x ^= a[i];
else s += a[i];
}
if(s < x) {
cout << -1 << endl;
return ;
}
if((s - x) & 1) {
cout << -1 << endl;
return ;
}
long long d = (s - x) / 2,an = (s - x) / 2;
if(d > a[1] || (x & d)) {
cout << -1 << endl;
return ;
}
for(int i = 50;i >= 0;i--) {
if((x >> i & 1) && !(d >> i & 1) && (an + (1LL << i)) <= a[1])
an += 1LL << i;
}
if(an == 0) {
cout << -1 << endl;
return ;
}
cout << a[1] - an << endl;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
int T;
T = 1;
while(T --) {
solve();
}
return 0;
}
(1)题目大意
给定你一些查询,Add查询把x插入到当前数组末尾,Delete查询删除当前末尾的数,若没有不输出,Save把某个数组的保存在第几页,Load加载到第几页的状态。
(2)解题思路
这个题肯定是个模拟,问题就在于你怎样保证回到之前记录的某一页的状态上去,并且在随后的操作不会更改以前的值呢?很明显我们的数组的值只能一直加,不能改,不然会乱,因此我们可以定义一个数组来表示第i个位置上一次是由哪一个位置改过来的,因此我们定义一个fa数组表示当前这个位置的上一个位置是哪一个,然后用map记录一下保存页数的下标即可。
(3)代码实现
#include "bits/stdc++.h"
using namespace std;
const int N = 5e5 + 10;
map <int,int> mp;
int A[N],f[N],idx,cur;
void solve()
{
int n;
cin >> n;
A[0] = -1;
while(n --) {
int x;string op;
cin >> op;
if(op[0] == 'A') {
cin >> x;
A[++ idx] = x;
f[idx] = cur;
cur = idx;
}
if(op[0] == 'S') {
cin >> x;
mp[x] = cur;
}
if(op[0] == 'D') if(cur > 0) cur = f[cur];
if(op[0] == 'L') {
cin >> x;
cur = mp[x];
}
cout << A[cur] << ' ';
}
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
int T;
T = 1;
while(T --) {
solve();
}
return 0;
}
四.E - Through Path (atcoder.jp)
(1)题目大意
给你n个点,n - 1条边,询问Q次,若ti=1,表示经过Aei这个点不经过Bei这个点的所有路径都加上x,若ti=2,表示经过Bei不经过Aei这个点的所有路径加上x,最后让你输出所有点的值。
(2)解题思路
分析第一个操作,我们假定以1为根。
第一种情况,若f[a] != b
第二种情况,若f[a] == b
分析第二个操作,显然就是第一个操作反着来一下,这里就不分析了。
(3)代码实现
#include "bits/stdc++.h"
#define PII pair<int,int>
#define fi first
#define se second
using namespace std;
const int N = 2e5 + 10;
PII e[N];
vector <int> G[N];
int fa[N];
long long s[N];
void bfs()
{
fa[1] = -1;
queue <int> que;
que.push(1);
while(que.size()) {
int v = que.front();
que.pop();
for(auto x:G[v]) {
if(fa[x]) continue;
fa[x] = v;
que.push(x);
}
}
}
void dfs(int u,long long c,int fat)
{
s[u] += c;
for(auto x:G[u]) {
if(x == fat) continue;
dfs(x,s[u],u);
}
}
void solve()
{
int n,q;
cin >> n;
for(int i = 1;i < n;i++) {
cin >> e[i].fi >> e[i].se;
G[e[i].fi].push_back(e[i].se);
G[e[i].se].push_back(e[i].fi);
}
bfs();
cin >> q;
while(q--) {
int t,i,v;
cin >> t >> i >> v;
int a,b;
if(t == 1) {
a = e[i].fi,b = e[i].se;
if(fa[a] == b) s[a] += v;
else s[1] += v,s[b] -= v;
}
else {
a = e[i].fi,b = e[i].se;
if(fa[a] == b) s[1] += v,s[a] -= v;
else s[b] += v;
}
}
dfs(1,0,0);
for(int i = 1;i <= n;i++) cout << s[i] << endl;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
int T;
T = 1;
while(T --) {
solve();
}
return 0;
}
(1)题目大意
(2) 解题思路
这个题赛时的时候想到怎么做了,但是没有调出来树状数组。我们观察到这题只需要计算答案,我们发现如果枚举左或者右都不太好,因为要跳两个,因此我们考虑枚举中间的,对于前面的就是预处理一个正序对,对于后面的预处理一个逆序对,答案就是。
(3)代码实现
#include "bits/stdc++.h"
using namespace std;
const int N = 1e6 + 10;
int seg[3][N],a[N],de[N],sc[N];
int n;
int lowbit(int x)
{
return x & -x;
}
int qry(int p,int x)
{
int res = 0;
while(x) {
res += seg[p][x];
x -= lowbit(x);
}
return res;
}
void add(int p,int x,int v)
{
while(x <= n) {
seg[p][x] += v;
x += lowbit(x);
}
}
int main()
{
cin >> n;
vector <int> nums;
for(int i = 1;i <= n;i++) {
cin >> a[i];
nums.push_back(a[i]);
}
sort(nums.begin(),nums.end());
nums.erase(unique(nums.begin(),nums.end()),nums.end());
for(int i = 1;i <= n;i++) a[i] = lower_bound(nums.begin(),nums.end(),a[i]) - nums.begin() + 1;
//求逆正序对
for(int i = 1;i <= n;i++) {
de[i] = i - 1 - qry(1,a[i]);
add(1,a[i],1);
}
//求正序对
for(int i = n;i >= 1;i--) {
sc[i] = qry(2,a[i]);
add(2,a[i],1);
}
long long ans = 0;
for(int i = 1;i <= n;i++) ans += 1LL * de[i] * sc[i];
cout << ans << endl;
return 0;
}
(1)题目大意
给你一颗n个节点的树,n - 1条边,你最多可以删除k-1条边,使得剩下的连通块异或起来的值一样。
(2)解题思路
我们考虑把所有节点的值异或起来,若为0,则说明中间一定存在两个连通块值相同,我们把他们的边砍掉即可。若不为0,我们则需要分出三个这样的联通块,若存在一个联通块等于整棵树的值,我们考虑把这条边砍掉,让cnt++,把这个节点的值赋值为0,若最后存在>=3,则说明一定可以。
(3)代码实现
#include "bits/stdc++.h"
using namespace std;
const int N = 1e5 + 10;
int s[N],cnt = 0;
vector <int> e[N];
int n,k,x = 0;
void dfs(int u,int fa)
{
for(auto v:e[u]) {
if(v == fa) continue;
dfs(v,u);
s[u] ^= s[v];
}
if(s[u] == x) s[u] = 0,cnt ++;
}
void solve()
{
cin >> n >> k;
x = cnt = 0;
for(int i = 1;i <= n;i++) {
cin >> s[i];
x ^= s[i];
e[i].clear();
}
for(int i = 1;i < n;i++) {
int u,v;
cin >> u >> v;
e[u].push_back(v);
e[v].push_back(u);
}
if(x == 0) {
cout << "YES" << endl;
return ;
}
if(k == 2) {
cout << "NO" << endl;
return ;
}
dfs(1,0);
if(cnt >= 3) cout << "YES" << endl;
else cout << "NO" << endl;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
int T;
cin >> T;
while(T --) {
solve();
}
return 0;
}
(1)题目大意
给你n个点,A有m1条边,B有m2条边,对于一对点(i,j)若他们没有公共的边,则可以连接,若有公共边则不可以连接,问你最多可以连接多少条?
(2)解题思路
我们首先枚举i[2-n]和1是否可以连边,若是两边都可以连,那么直接连上即可,若不可以连接,则说明至少有一个集合和1有连边。我们分别枚举两边没和1连边的集合,然后给他们连接上即可。
(3)代码实现
#include "bits/stdc++.h"
#define PII pair<int,int>
using namespace std;
const int N = 1e5 + 10;
int f[2][N];
int find(int x,int p)
{
return f[p][x] == x ? x : f[p][x] = find(f[p][x],p);
}
void solve()
{
int n,m1,m2;
cin >> n >> m1 >> m2;
int u,v;
for(int i = 1;i <= n;i++) f[0][i] = f[1][i] = i;
for(int i = 1;i <= m1;i++) {
cin >> u >> v;
int fu = find(u,0),fv = find(v,0);
f[0][fv] = fu;
}
for(int i = 1;i <= m2;i++) {
cin >> u >> v;
int fu = find(u,1),fv = find(v,1);
f[1][fv] = fu;
}
vector <PII> ans;
for(int i = 2;i <= n;i++) {
if(find(i,0) != find(1,0) && find(i,1) != find(1,1)) {
int fu1 = find(i,0),fv1 = find(1,0);
int fu2 = find(i,1),fv2 = find(1,1);
f[0][fu1] = fv1;
f[1][fu2] = fv2;
ans.push_back({1,i});
}
}
set <int> st[2];
for(int i = 2;i <= n;i++) {
if(find(i,0) != find(1,0)) st[0].insert(find(i,0));
if(find(i,1) != find(1,1)) st[1].insert(find(i,1));
}
if(st[0].size() > st[1].size()) swap(st[0],st[1]);
for(auto it0 = st[0].begin(),it1 = st[1].begin();it0 != st[0].end();it0 ++,it1 ++) ans.push_back({*it0,*it1});
cout << ans.size() << endl;
for(auto x:ans) cout << x.first << ' ' << x.second << endl;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
int T;
T = 1;
while(T --) {
solve();
}
return 0;
}
八.E - Crystal Switches (atcoder.jp)
(1).题目大意
给定一个由N个顶点和M条边组成的无向图。Fori = 1,2,.., M,第i条边是连接顶点lu的无向边;和你;当ai = 1时,初始是可以通过的如果a;= 0。此外,在K个顶点上有开关:顶点S1,顶点s2,…takahashi最初在顶点1上,并将重复执行以下两个动作之一:Move或Hit Switch,他可以每次选择这两个动作,想选择多少次就选择多少次。•移动:通过一条边选择一个与他当前所在顶点相邻的顶点,并移动到该顶点。点击开关:如果他当前所在的顶点上有一个开关,点击它。这将反转图中每条边的可通过性。也就是说,可通过的边会变成不可通过的边,反之亦然。确定Takahashi是否可以到达顶点N,如果可以,打印他在到达顶点N之前执行Move的最小可能次数。
(2).解题思路
因为有个撞击改变图的边,我们考虑每次撞击后转入另一个图,因此考虑分层图解决此类题目。
(3).代码实现
// Problem: E - Crystal Switches
// Contest: AtCoder - Daiwa Securities Co. Ltd. Programming Contest 2022 Autumn (AtCoder Beginner Contest 277)
// URL: https://atcoder.jp/contests/abc277/tasks/abc277_e
// Memory Limit: 1024 MB
// Time Limit: 2000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include "bits/stdc++.h"
#define rep(i, z, n) for (int i = z; i <= n; i++)
#define per(i, n, z) for (int i = n; i >= z; i--)
#define ll long long
#define db double
#define PII pair<int, int>
#define fi first
#define se second
#define vi vector<int>
#define yes cout << "YES" << endl;
#define no cout << "NO" << endl;
using namespace std;
const int N = 2e5 + 10;
struct node
{
int v, sta, s;
};
vector<PII> G[N];
int t[N], dis[N][2];
int n, m, q;
void bfs()
{
memset(dis, 0x3f, sizeof(dis));
queue<node> que;
que.push({1, 0, 0});
while (que.size())
{
node v = que.front();
que.pop();
// cout << v.v << ',' << v.sta << ',' << v.s << endl;
for (auto x : G[v.v])
{
int now = x.fi, sta = x.se, s = v.s;
if ((sta + v.sta) % 2 == 1 && t[v.v] == 0)
{
continue;
}
if (t[v.v])
{
if ((sta & 1) != (v.sta & 1))
{
if (dis[now][(v.sta + 1) & 1] > s + 1)
{
dis[now][(v.sta + 1) & 1] = s + 1;
que.push({now, v.sta + 1, s + 1});
}
}
else
{
if (dis[now][v.sta & 1] > s + 1)
{
dis[now][v.sta & 1] = s + 1;
que.push({now, v.sta, s + 1});
}
}
}
else
{
if (dis[now][v.sta & 1] > s + 1)
{
dis[now][v.sta & 1] = s + 1;
que.push({now, v.sta, s + 1});
}
}
}
}
if (min(dis[n][0], dis[n][1]) >= dis[0][0])
{
cout << -1 << endl;
}
else
{
cout << min(dis[n][0], dis[n][1]) << endl;
}
}
void solve()
{
cin >> n >> m >> q;
for (int i = 1; i <= m; i++)
{
int u, v, w;
cin >> u >> v >> w;
G[u].push_back({v, !w});
G[v].push_back({u, !w});
}
while (q--)
{
int x;
cin >> x;
t[x] = 1;
}
bfs();
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
int T = 1;
// cin >> T;
while (T--)
solve();
return 0;
}
九.Ex - Constrained Sums (atcoder.jp)
(1)题目大意
确定是否有N个整数的序列X = (X1, X2,…), XN)满足以下所有条件,如果存在则构造一个这样的序列。满足Ai + Bi >= Li Ai + Bi <= Ri
(2)解题思路
经典2—SAT问题解决此类包含有逆否命题的题目。如果Ai能选i,Bi能选j,那么能推出来Bi不选jAi也不能选i,对于每一个位置都构造如此序列,跑一个tarjan后判断答案是否存在,若对于Ai和Ai+1存在同一个环中,说明答案不可能构成,输出-1。否则取拓扑序最大的那个输出,因为dfs是逆拓扑序的,我们需要限制最多的开始。
(3)代码实现
// Problem: Ex - Constrained Sums
// Contest: AtCoder - Daiwa Securities Co. Ltd. Programming Contest 2022 Autumn (AtCoder Beginner Contest 277)
// URL: https://atcoder.jp/contests/abc277/tasks/abc277_h
// Memory Limit: 1024 MB
// Time Limit: 4000 ms
// Powered by CP Editor (https://cpeditor.org)
#include "bits/stdc++.h"
#define rep(i, z, n) for (int i = z; i <= n; i++)
#define per(i, n, z) for (int i = n; i >= z; i--)
#define ll long long
#define db double
#define PII pair<int, int>
#define fi first
#define se second
#define vi vector<int>
#define yes cout << "YES" << endl;
#define no cout << "NO" << endl;
using namespace std;
const int N = 4e6 + 10, M = 2e6 + 10;
vector<int> G[N];
int n, m, q;
int get(int x, int y, int t)
{
return (((x - 1) * m + (y - 1)) << 1) | t;
}
void add(int x, int y)
{
G[x].push_back(y);
if (x != (y ^ 1))
G[y ^ 1].push_back(x ^ 1);
}
void init(int x)
{
for (int i = 1; i < m; i++)
{
add(get(x, i, 0), get(x, i + 1, 0));
}
}
void merge1(int x, int y, int val)
{
for (int i = 1; i <= m; i++)
{
if (i + m <= val)
{
add(get(x, i, 0), get(x, i, 1));
add(get(y, i, 0), get(y, i, 1));
}
else if (i <= val)
{
add(get(x, i, 0), get(y, val - i + 1, 1));
}
}
}
void merge2(int x, int y, int val)
{
for (int i = 1; i <= m; i++)
{
if (i > val)
{
add(get(x, i, 1), get(x, i, 0));
add(get(y, i, 1), get(y, i, 0));
}
else if (i + m > val)
{
add(get(x, i, 1), get(y, val - i + 1, 0));
}
}
}
int dfn[M], low[M], stk[M], ins[M], bel[M], top, tim, cor;
void tarjan(int now)
{
dfn[now] = low[now] = ++tim;
ins[now] = true;
stk[++top] = now;
for (auto x : G[now])
{
if (!dfn[x])
{
tarjan(x);
low[now] = min(low[now], low[x]);
}
else if (ins[x])
low[now] = min(low[now], dfn[x]);
}
if (low[now] == dfn[now])
{
++cor;
do
{
bel[stk[top]] = cor;
ins[stk[top--]] = false;
} while (stk[top + 1] != now);
}
}
void solve()
{
cin >> n >> m >> q;
int tot = 2 * n * m;
for (int i = 1; i <= n; i++)
init(i);
// 1表示选,0表示不选
for (int i = 1; i <= q; i++)
{
int x, y, l, r;
cin >> x >> y >> l >> r;
merge1(x, y, l);
merge2(x, y, r);
}
for (int i = 0; i < tot; i++)
{
if (!dfn[i])
{
tarjan(i);
}
}
for (int i = 0; i < tot; i += 2)
{
if (bel[i] == bel[i + 1])
{
cout << -1 << endl;
return;
}
}
for (int i = 1; i <= n; i++)
{
int ans = 0;
for (int j = 1; j <= m; j++)
{
if (bel[get(i, j, 1)] < bel[get(i, j, 0)])
{
ans = j;
// break;
}
}
cout << ans << ' ';
}
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
int T = 1;
// cin >> T;
while (T--)
solve();
return 0;
}
十.E - Cheating Amidakuji (atcoder.jp)
(1)题目大意
给你一个A数组和B数组,A数组初始化为1-n,问你有M次操作,每次交换和,问你对于第i个位置操作不执行,此时1在哪个位置。
(2)解题思路
考虑模拟,我们从前往后 枚举每一次操作,并且把每次操作的还没操作的结果记录下来,最后跑完一遍后,重新更新一下每个数的位置。最后重新遍历一遍操作前的结果,如果当前操作了1,那么我们答案就是另一个数,否则就是直接输出1的新位置就行了。
(3)代码实现
// Problem: E - Cheating Amidakuji
// Contest: AtCoder - TOYOTA SYSTEMS Programming Contest 2022(AtCoder Beginner Contest 279)
// URL: https://atcoder.jp/contests/abc279/tasks/abc279_e
// Memory Limit: 1024 MB
// Time Limit: 2000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include "bits/stdc++.h"
#define rep(i, z, n) for (int i = z; i <= n; i++)
#define per(i, n, z) for (int i = n; i >= z; i--)
#define ll long long
#define db double
#define PII pair<int, int>
#define fi first
#define se second
#define vi vector<int>
#define yes cout << "YES" << endl;
#define no cout << "NO" << endl;
using namespace std;
const int N = 2e5 + 10;
PII last[N];
int a[N], pos[N], npos[N];
void solve()
{
int n, m;
cin >> n >> m;
for (int i = 1; i <= n; i++)
{
pos[i] = i;
}
int x;
for (int i = 1; i <= m; i++)
{
cin >> x;
last[i] = {pos[x], pos[x + 1]};
swap(pos[x], pos[x + 1]);
}
for (int i = 1; i <= n; i++)
{
npos[pos[i]] = i;
}
for (int i = 1; i <= m; i++)
{
if (last[i].fi == 1)
cout << npos[last[i].se] << endl;
else if (last[i].se == 1)
cout << npos[last[i].fi] << endl;
else
cout << npos[1] << endl;
}
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
int T = 1;
// cin >> T;
while (T--)
solve();
return 0;
}
十一.F - Pay or Receive (atcoder.jp)
(1)题目大意
给你一个图和一些边权,然后给你q个查询,如果从u-v的路可以无穷大就输出inf,如果u-v没有路,输出nan,否则输出从u-v的最大的距离。
(2)解题思路
1.对于那些不能到达的路,可以用并查集直接判断出来
2.对于那些无穷大的路,可以用dfs预处理出环来判断
3.对于那些可以直接到的路,并且没有环的路,我们可以dfs处理出来。
实际上这个最大值是多余的,因为如果要取最大值,那么说明从u-v存在多条路,那么肯定会构成无穷大环,显然是不行的,因此第三个条件就只有一条路。
(3)代码实现
// Problem: F - Pay or Receive
// Contest: AtCoder - Denso Create Programming Contest 2022 Winter(AtCoder Beginner Contest 280)
// URL: https://atcoder.jp/contests/abc280/tasks/abc280_f
// Memory Limit: 1024 MB
// Time Limit: 2000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include "bits/stdc++.h"
#define rep(i, z, n) for (int i = z; i <= n; i++)
#define per(i, n, z) for (int i = n; i >= z; i--)
#define ll long long
#define db double
#define PII pair<int, int>
#define fi first
#define se second
#define vi vector<int>
#define yes cout << "YES" << endl;
#define no cout << "NO" << endl;
using namespace std;
const int N = 1e5 + 10;
const int inf = 0x3f3f3f3f;
struct node
{
int v, w;
};
bool vis[N], cycle[N], c;
ll dis[N], dp[N];
vector<node> G[N];
struct UFS
{
int f[N];
UFS()
{
init();
}
void init()
{
//常量递增
iota(f, f + N, 0);
}
int find(int x)
{
return x == f[x] ? x : f[x] = find(f[x]);
}
void merge(int x, int y)
{
int fx = find(x), fy = find(y);
f[fx] = fy;
}
bool same(int x, int y)
{
return find(x) == find(y);
}
} ufs;
void dfs1(int now, ll d = 0)
{
if (vis[now])
{
if (dis[now] != d)
{
c = true;
}
return;
}
vis[now] = true, dis[now] = d;
for (auto x : G[now])
{
dfs1(x.v, x.w + d);
}
}
void dfs2(int now)
{
vis[now] = true;
for (auto x : G[now])
{
if (!vis[x.v])
{
dp[x.v] = dp[now] + x.w;
dfs2(x.v);
}
}
}
void solve()
{
int n, m, q;
cin >> n >> m >> q;
rep(i, 1, m)
{
int u, v, w;
cin >> u >> v >> w;
G[u].push_back({v, w});
G[v].push_back({u, -w});
if (!ufs.same(u, v))
{
ufs.merge(u, v);
}
}
//找环
rep(i, 1, n)
{
int j = ufs.find(i);
if (!vis[j])
{
c = false;
dfs1(j);
cycle[j] = c;
}
}
// dp找路
memset(vis, 0, sizeof(vis));
rep(i, 1, n)
{
int j = ufs.find(i);
if (cycle[j] || vis[j])
continue;
else
{
dfs2(j);
}
}
while (q--)
{
int u, v;
cin >> u >> v;
if (!ufs.same(u, v))
{
cout << "nan" << endl;
}
else if (cycle[ufs.find(u)])
{
cout << "inf" << endl;
}
else
{
cout << -dp[u] + dp[v] << endl;
}
}
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
int T = 1;
// cin >> T;
while (T--)
solve();
return 0;
}
十二.E - Destruction (atcoder.jp)
(1)题目大意
给我们一个无向连通图,我们每删除一条边我们就能得到其中的边权,问我们在保证图联通的情况下,最大能获得多少价值?
(2)解题思路
考虑把边权从小到大排序,对于前面的边我们用并查集维护起来,若当前枚举的边的x和y不属于同一个集合,我们考虑把他们连起来,若属于一个集合了,边为正,我们则删除这条边,加上这条边的权值,若边为负,我们不需要考虑。
(3)代码实现
// Problem: E - Destruction
// Contest: AtCoder - AtCoder Beginner Contest 218
// URL: https://atcoder.jp/contests/abc218/tasks/abc218_e
// Memory Limit: 1024 MB
// Time Limit: 2000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include "bits/stdc++.h"
#define rep(i, z, n) for (int i = z; i <= n; i++)
#define per(i, n, z) for (int i = n; i >= z; i--)
#define ll long long
#define db double
#define PII pair<int, int>
#define fi first
#define se second
#define vi vector<int>
#define yes cout << "YES" << endl;
#define no cout << "NO" << endl;
using namespace std;
const int N = 2e5 + 10;
struct node
{
int u, v, w;
bool operator<(const node &other) const
{
return w < other.w;
}
} e[N];
struct UFS
{
int f[N];
UFS()
{
init();
}
void init()
{
iota(f, f + N, 0);
}
int find(int x)
{
return x == f[x] ? x : f[x] = find(f[x]);
}
bool same(int x, int y)
{
return find(x) == find(y);
}
void merge(int x, int y)
{
int fx = find(x), fy = find(y);
f[fx] = fy;
}
} ufs;
void solve()
{
int n, m;
cin >> n >> m;
for (int i = 1; i <= m; i++)
{
cin >> e[i].u >> e[i].v >> e[i].w;
}
sort(e + 1, e + 1 + m);
ll ans = 0;
for (int i = 1; i <= m; i++)
{
if (!ufs.same(e[i].u, e[i].v))
{
ufs.merge(e[i].u, e[i].v);
}
else if (e[i].w >= 0)
{
ans += e[i].w;
}
}
cout << ans << endl;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
int T = 1;
// cin >> T;
while (T--)
solve();
return 0;
}
十三.F - Blocked Roads (atcoder.jp)
(1)题目大意
给我们一个有向图,可能有自环和重边,有M条边,问我们每次把第i条边去掉后,从1-n的最短距离是多少,如果不可达输出-1,否则输出最短距离。
(2)解题思路
考虑所有边跑一遍djikstra,如果不可达n,那么输出m行-1,否则我们把dijkstra跑出来的最短路上的边打上记号,我们考虑删除不是最短路的边的时候,那么我们的答案一定是第一遍dijkstra跑出来的距离,否则我们需要把这条边设置为不可达状态,继续跑一遍dij。一遍dji的复杂度最多是nlogn,最短路长度最多n,那么我们只需要n^2logn即可跑完。
(3)代码实现
// Problem: F - Blocked Roads
// Contest: AtCoder - AtCoder Beginner Contest 218
// URL: https://atcoder.jp/contests/abc218/tasks/abc218_f
// Memory Limit: 1024 MB
// Time Limit: 2000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include "bits/stdc++.h"
#define rep(i, z, n) for (int i = z; i <= n; i++)
#define per(i, n, z) for (int i = n; i >= z; i--)
#define ll long long
#define db double
#define PII pair<int, int>
#define fi first
#define se second
#define endl '\n'
#define vi vector<int>
#define yes cout << "YES" << endl;
#define no cout << "NO" << endl;
using namespace std;
const int N = 2e6 + 10;
const int inf = 0x3f3f3f3f;
int he[410], idx, f[N], dis[410], pre[N], vis[N];
struct Edge
{
int nt, to;
} e[N];
int n, m;
void add(int u, int v)
{
e[++idx] = {he[u], v};
he[u] = idx, f[idx + 1] = u;
}
int dij(int lim)
{
memset(dis, 0x3f, sizeof(dis));
dis[1] = 0;
queue<int> que;
que.push(1);
while (que.size())
{
int v = que.front();
que.pop();
if (v == n)
{
return dis[v];
}
for (int i = he[v]; ~i; i = e[i].nt)
{
int j = e[i].to;
if (i == lim)
continue;
if (dis[j] > dis[v] + 1)
{
dis[j] = dis[v] + 1;
pre[j] = i;
que.push(j);
}
}
}
return -1;
}
void solve()
{
memset(he, -1, sizeof(he));
cin >> n >> m;
for (int i = 1; i <= m; i++)
{
int u, v;
cin >> u >> v;
add(u, v);
}
int dist = dij(0);
if (dist == -1)
{
for (int i = 1; i <= m; i++)
cout << dist << endl;
return;
}
int x = n;
do
{
int pe = pre[x];
vis[pe] = true;
x = f[pe + 1];
} while (x != 1);
for (int i = 1; i <= m; i++)
{
if (!vis[i])
cout << dist << endl;
else
cout << dij(i) << endl;
}
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
int T = 1;
// cin >> T;
while (T--)
solve();
return 0;
}
(1)题目大意
给你一个矩阵问你每次可以翻转一行或者一列,能不能把这个矩阵变成非递减矩阵。
(2)解题思路
考虑先把某一行变成全部一样,然后看其他行是否是全部相同,若全部相同则可以变成全0矩阵,若只有一行相同并且那一行就分成两段则那一行前面全是0后面全是1即可,否则不可能构造出来。
(3)代码实现
#include "bits/stdc++.h"
#define rep(i,z,n) for(int i = z;i <= n; i++)
#define per(i,n,z) for(int i = n;i >= z; i--)
#define PII pair<int,int>
#define fi first
#define se second
#define vi vector<int>
#define vl vector<ll>
#define pb push_back
#define sz(x) (int)x.size()
#define all(x) (x).begin(),(x).end()
using namespace std;
using ll = long long;
const int N = 210;
int a[N][N],b[N][N],invc[N],invr[N];
int n,m;
bool check()
{
int diff = 0,row = -1;
rep(i,1,n) rep(j,1,m) if(invc[j]) b[i][j] ^= 1;
rep(i,1,n) {
int spice = 0;
bool now = false;
rep(j,2,m) {
if(b[i][j] != b[i][j - 1]) spice ++;
if(b[i][j] != b[i][1]) {
now = true;
row = i;
}
}
diff += now;
// cout << "diff" << ',' << diff << ',' << spice << endl;
if(diff >= 2 || spice >= 2) return false;
}
if(!diff) {
rep(i,1,n) {
invr[i] = b[i][1];
}
}
else {
rep(i,1,n) {
if(i <= row) invr[i] = b[i][1];
else invr[i] = !b[i][1];
}
}
return true;
}
void solve()
{
cin >> n >> m;
rep(i,1,n) rep(j,1,m) cin >> a[i][j];
//枚举向当前行靠齐
rep(i,1,n) {
rep(j,1,n) rep(k,1,m) b[j][k] = a[j][k];
memset(invr,0,sizeof(invr));
memset(invc,0,sizeof(invc));
rep(j,2,m) if(b[i][j] != b[i][1]) invc[j] ^= 1;
if(check()) {
cout << "YES" << endl;
rep(j,1,n) cout << invr[j];
cout << endl;
rep(j,1,m) cout << invc[j];
return;
}
}
cout << "NO" << endl;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
int T = 1;
// cin >> T;
while(T --) solve();
return 0;
}
十五.C - All Pair Digit Sums (atcoder.jp)
(1)题目大意
计算A[i]+A[j]的数位和
(2)解题思路
考虑设置函数F(A+B) = F(A) + F(B) - 9G(A+B),可知每一次进位就会使最后数位和减少9,因此我们先算出所有数位和,然后减去减少多少数位和就行,然后枚举15次,用双指针算出来即可。
(3)代码实现
#include "bits/stdc++.h"
#define rep(i,z,n) for(int i = z;i <= n; i++)
#define per(i,n,z) for(int i = n;i >= z; i--)
#define PII pair<int,int>
#define fi first
#define se second
#define vi vector<int>
#define vl vector<ll>
#define pb push_back
#define sz(x) (int)x.size()
#define all(x) (x).begin(),(x).end()
using namespace std;
using ll = long long;
const int N = 2e5 + 10;
ll a[N],b[N],pw = 1;
void solve()
{
ll ans = 0,n,x;
cin >> n;
rep(i,1,n) {
cin >> a[i];
x = a[i];
rep(j,0,15) {
ans += (x % 10) * n * 2;
x /= 10;
}
}
rep(i,1,16) {
pw *= 10;
for(int j = 1;j <= n;j ++) {
b[j] = a[j] % pw;
}
sort(b + 1,b + 1 + n);
for(int l = 1,r = n;l <= n;l ++) {
while(b[r] + b[l] >= pw) r --;
ans -= 9 * (n - r);
}
}
cout << ans << endl;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
int T = 1;
// cin >> T;
while(T --) solve();
return 0;
}
十六.F-球球大作战_牛客小白月赛66 (nowcoder.com)
(1)题目大意
(2)解题思路
对a进行排序,二分1-n看最少要到哪个点后才能满足后面所有都能大于其他消耗地最优地情况,对于其他求消耗每次都是拿出最大和最小地一半然后塞入multiset,然后比较当前二分这个是否大于这个值即可。
(3)代码实现
#include "bits/stdc++.h"
#define rep(i,z,n) for(int i = z;i <= n; i++)
#define per(i,n,z) for(int i = n;i >= z; i--)
#define PII pair<int,int>
#define fi first
#define se second
#define vi vector<int>
#define vl vector<ll>
#define pb push_back
#define sz(x) (int)x.size()
#define all(x) (x).begin(),(x).end()
using namespace std;
using ll = long long;
const int N = 2e5 + 10;
int a[N],b[N],ans[N];
int n;
bool check(int k)
{
multiset<int>mt;
rep(i,1,n) {
if(i == k) continue;
mt.insert(a[b[i]]);
}
while(sz(mt)>1){
int v1=*(prev(mt.end()));
mt.erase(prev(mt.end()));
int v2=*(prev(mt.end()));
mt.erase(prev(mt.end()));
mt.insert((v1+v2)/2);
}
return a[b[k]] >= (*mt.begin());
}
void solve()
{
cin >> n;
rep(i,1,n) cin >> a[i];
rep(i,1,n) b[i] = i;
sort(b + 1,b + 1 + n,[&](int x,int y){
return a[x] < a[y];
});
int l = 1,r = n;
while(l <= r) {
int mid = (l + r) >> 1;
if(check(mid)) r = mid - 1;
else l = mid + 1;
}
rep(i,l,n) ans[b[i]] = 1;
rep(i,1,n) cout << ans[i];
cout << endl;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
int T = 1;
// cin >> T;
while(T --) solve();
return 0;
}
(1)题目大意
给你n个点m条边,问你跑出[1,n]的dfs序需要再建立多少条边
(2)解题思路
从第1个点开始往下dfs,若当前点的下一个节点有和自己挨着的,则直接走下去即可,否则答案+1,从最小那个点继续开始往下走即可,若当前子树都走完了还是没有走到n,则答案+1从下一个新的点开始往下dfs即可。
#include <iostream>
#include <map>
#include <set>
#include <vector>
#include <cstring>
#include <cstdlib>
#include <queue>
#include <stack>
#include <algorithm>
#include <cmath>
#include <unordered_map>
#include <deque>
#include <bitset>
#define rep(i,z,n) for(int i = z;i <= n; i++)
#define per(i,n,z) for(int i = n;i >= z; i--)
#define PII pair<int,int>
#define fi first
#define se second
#define vi vector<int>
#define vl vector<ll>
#define pb push_back
#define sz(x) (int)x.size()
#define all(x) (x).begin(),(x).end()
using namespace std;
using ll = long long;
const int N = 1e5 + 10;
int cnt[N],ans = 0,cur = 1;
vector <int> G[N];
void dfs(int now)
{
if(cur == now) cur ++;
while(cnt[now] < sz(G[now]) && G[now][cnt[now]] < cur) cnt[now] ++;
while(cnt[now] < sz(G[now])) {
if(cur != G[now][cnt[now]]) ans ++;
dfs(cur);
while(cnt[now] < sz(G[now]) && G[now][cnt[now]] < cur) cnt[now] ++;
}
}
void solve()
{
int n,m;
cin >> n >> m;
cur = 1;
rep(i,1,n) {
G[i].clear();
cnt[i] = 0;
}
ans = 0;
rep(i,1,m) {
int u,v;
cin >> u >> v;
if(u == v) continue;
G[min(u,v)].pb(max(u,v));
}
rep(i,1,n) sort(all(G[i]));
while(cur <= n) {
dfs(cur);
ans ++;
}
cout << ans - 1 << endl;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
int T = 1;
cin >> T;
while(T --) solve();
return 0;
}
十八.F-小d和送外卖_牛客小白月赛70 (nowcoder.com)
(1)题目大意
(2)解题思路
考虑树上dp,dp[i][j]表示第i个节点这颗子树取消j个订单的最小价值是多少,然后考虑转移,为了防止重复转移,定义一个新的数组ndp,ndp[j + k] = min(ndp[j + k],dp[now][j] + dp[son][k] + k != siz[son]);
(3)代码实现
#include <iostream>
#include <map>
#include <set>
#include <vector>
#include <cstring>
#include <cstdlib>
#include <queue>
#include <stack>
#include <algorithm>
#include <cmath>
#include <unordered_map>
#include <deque>
#include <bitset>
#define rep(i,z,n) for(int i = z;i <= n; i++)
#define per(i,n,z) for(int i = n;i >= z; i--)
#define PII pair<int,int>
#define fi first
#define se second
#define vi vector<int>
#define vl vector<ll>
#define pb push_back
#define sz(x) (int)x.size()
#define all(x) (x).begin(),(x).end()
using namespace std;
using ll = long long;
const int N = 2e5 + 10;
vector <int> G[N];
int dp[N][51],siz[N],ndp[51];
int n,k;
void dfs(int now,int fa)
{
for(auto x : G[now]) {
if(x == fa) continue;
dfs(x,now);
siz[now] += siz[x];
memset(ndp,0x3f,sizeof(ndp));
for(int i = 0;i <= k;i ++) {
for(int j = 0;j <= siz[x] && i + j <= k;j ++) {
int w = j != siz[x];
ndp[i + j] = min(ndp[i + j],dp[now][i] + dp[x][j] + w);
}
}
swap(ndp,dp[now]);
}
}
void solve()
{
cin >> n >> k;
rep(i,1,n - 1) {
int u,v;
cin >> u >> v;
G[u].pb(v),G[v].pb(u);
}
int q,x;
cin >> q;
while(q --) {
cin >> x;
siz[x] ++;
}
dfs(1,0);
int ans = 2e9;
for(int i = 0;i <= k;i ++) ans = min(ans,dp[1][i] * 2);
cout << ans << endl;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
int T = 1;
// cin >> T;
while(T --) solve();
return 0;
}
十九.L-Karashi的电灯泡_北华大学第九届程序设计竞赛(同步赛) (nowcoder.com)
(1)题目大意
(2)解题思路
很容易知道,其实ai的状态之和a[i + 1]有关,因此我们只需要确定几个特殊的位置然后枚举即可,比如a1的按和不按,an这个状态是由b1还是c1按,然后如果an不亮b1和c1可以选择是否同时按或者不按。
(3)代码实现
#include<bits/stdc++.h>
#define sz(x) (int) x.size()
#define rep(i,z,n) for(int i = z;i <= n; i++)
#define per(i,n,z) for(int i = n;i >= z; i--)
#define pii pair<int,int>
#define fi first
#define se second
#define vi vector<int>
#define vl vector<ll>
#define pb push_back
#define all(x) (x).begin(),(x).end()
#define endl '\n'
using namespace std;
using ll = long long;
const int N = 5e5 + 10;
const ll mod = 1e9 + 7;
int a[N],d[N],b[N],c[N];
int A[N],D[N],B[N],C[N];
int n,m,k;
const int inf = 2e9;
int calc1(int op)
{
int res = 0;
rep(i,2,n - 1) {
if(a[i - 1]) {
a[i - 1] ^= 1;a[i] ^= 1;
a[i + 1] ^= 1;res ++;
}
}
if(n > 1) {
if(a[n - 1]) {
a[n] ^= 1;a[n - 1] ^= 1;
b[1] ^= 1;c[1] ^= 1;
res ++;
}
}
if(a[n]) {
res ++;
a[n] ^= 1;b[1] ^= 1;
if(m == 1) d[1] ^= 1;
else b[2] ^= 1;
}
else if(op) {
res += 2;
b[1] ^= 1;c[1] ^= 1;
if(m != 1) b[2] ^= 1,c[2] ^= 1;
}
rep(i,2,m - 1) {
if(b[i - 1]) {
b[i - 1] ^= 1;b[i] ^= 1;
b[i + 1] ^= 1;res ++;
}
if(c[i - 1]) {
c[i - 1] ^= 1;c[i] ^= 1;
c[i + 1] ^= 1;res ++;
}
}
if(m > 1) {
if(b[m - 1]) {
b[m - 1] ^= 1;b[m] ^= 1;
d[1] ^= 1;res ++;
}
if(c[m - 1]) {
c[m - 1] ^= 1;c[m] ^= 1;
d[1] ^= 1;res ++;
}
}
if(b[m] ^ c[m]) return inf;
if(b[m]) {
b[m] ^= 1;c[m] ^= 1;
d[1] ^= 1;d[2] ^= 1;
res ++;
}
rep(i,2,k) {
if(d[i - 1]) {
d[i - 1] ^= 1;d[i] ^= 1;
d[i + 1] ^= 1;res ++;
}
}
if(d[k]) return inf;
return res;
}
int calc2(int op)
{
rep(i,1,m) swap(b[i],c[i]);
return calc1(op);
}
void clear()
{
rep(i,1,n) a[i] = A[i];
rep(i,1,m) {
b[i] = B[i];
c[i] = C[i];
}
rep(i,1,k) d[i] = D[i];
}
void do1()
{
a[1] ^= 1;
int ans = inf;
if(n == 1) {
b[1] ^= 1;
c[1] ^= 1;
}else a[2] ^= 1;
}
void solve(){
cin >> n >> m >> k;
string s1,s2,s3,s4;
cin >> s1 >> s2 >> s3 >> s4;
for(int i = 0;i < n;i ++) A[i + 1] = s1[i] - '0';
for(int i = 0;i < m;i ++) B[i + 1] = s2[i] - '0';
for(int i = 0;i < m;i ++) C[i + 1] = s3[i] - '0';
for(int i = 0;i < k;i ++) D[i + 1] = s4[i] - '0';
int ans = inf;
clear();
do1();
ans = min(ans,calc1(0) + 1);
clear();
do1();
ans = min(ans,calc1(1) + 1);
clear();
do1();
ans = min(ans,calc2(0) + 1);
clear();
do1();
ans = min(ans,calc2(1) + 1);
clear();
ans = min(ans,calc1(0));
clear();
ans = min(ans,calc1(1));
clear();
ans = min(ans,calc2(0));
clear();
ans = min(ans,calc2(1));
if(ans == inf) cout << "NO" << '\n';
else {
cout << "YES" << '\n';
cout << ans << '\n';
}
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
int T = 1;
while(T --) solve();
}