周一
A. Luntik and Concerts(思维)
比赛的时候写的比较复杂,分类讨论
注意a, b, c >= 1
另S = a + b * 2 + c * 3
那么可以组成0~S的任何数
可以用贪心放,先放3,再放2,再放1,这样一定可以放完
可以讨论每次3有没有放完,2有没有放完
所以答案就看S的奇偶就行了
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
typedef long long ll;
int main()
{
int T; scanf("%d", &T);
while(T--)
{
ll a, b, c;
scanf("%lld%lld%lld", &a, &b, &c);
ll sum = a + b * 2 + c * 3;
printf("%d\n", sum % 2);
}
return 0;
}
D. Vupsen, Pupsen and 0(构造)
很快想到了构造方法,处理奇数的部分出了点问题
我用的式子里有两个数相减,没有考虑可能为0,交上去WA了
于是我就干脆找两个不相同的数相减,特殊情况所有数都相同很好处理
但是我这样写绝对值会变大,就不知道该怎么处理
后来不管了先交,结果AC了
之后看题解发现,如果是奇数情况,最大值一定是1e5-1,也就是最大值-1
所以绝对值多一个数是没关系的。比赛时没想到
还有一点是,构造的时候可以选择两个数相减,也可以选择两个数相加
选两个数相加不为0会好点,因为三个数里面,都不为0,一定存在两个数相加不为0
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 1e5 + 10;
int a[N], b[N], n;
int main()
{
int T; scanf("%d", &T);
while(T--)
{
scanf("%d", &n);
_for(i, 1, n) scanf("%d", &a[i]);
if(n % 2 == 0)
{
_for(i, 1, n)
{
if(i <= n / 2) printf("%d ", -a[n - i + 1]);
else printf("%d ", a[n - i + 1]);
}
puts("");
}
else
{
int p1 = 1, p2 = -1, p3;
_for(i, 2, n)
if(a[p1] != a[i])
{
p2 = i;
break;
}
if(p2 == -1)
{
printf("2 ");
_for(i, 2, n / 2) printf("1 ");
_for(i, n / 2 + 1, n) printf("-1 ");
puts("");
continue;
}
_for(i, 1, n)
if(i != p1 && i != p2)
{
p3 = i;
break;
}
_for(i, 1, n) b[i] = 0;
int A1 = a[p1], A2 = a[p2], A3 = a[p3];
b[p1] = -A3;
b[p2] = A3;
b[p3] = A1 - A2;
vector<int> ve;
_for(i, 1, n)
if(!b[i])
ve.push_back(i);
for(int i = 0; i < ve.size(); i += 2)
{
int l = ve[i], r = ve[i + 1];
b[l] = a[r];
b[r] = -a[l];
}
_for(i, 1, n) printf("%d ", b[i]); puts("");
}
}
return 0;
}
F1. Korney Korneevich and XOR (easy version)(dp)
这是一道dp
看数据范围,可以n * v的 也就是两个循环
这里的关键是怎么保证a的值递增
也就是i < j 且ai < aj
i < j用循环1到n来保证
ai < aj的话,枚举所有异或值,记录每一个异或值对应的最小的a[i]
如果ai < aj就可以转移
最后注意a最大值为500,但异或可能变大,到最接近它的2 ^ n - 1 也就是511
枚举值域的时候注意
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 1e5 + 10;
int dp[N], a[N], n;
int main()
{
scanf("%d", &n);
_for(i, 1, n) scanf("%d", &a[i]);
memset(dp, 0x3f, sizeof dp);
dp[0] = -1;
_for(i, 1, n)
_for(j, 0, 512)
if(dp[j] < a[i])
dp[j ^ a[i]] = min(dp[j ^ a[i]], a[i]);
vector<int> ans;
_for(i, 0, 512)
if(dp[i] < 1e9)
ans.push_back(i);
printf("%d\n", ans.size());
for(int x: ans) printf("%d ", x); puts("");
return 0;
}
周二
Tree Constructer(构造 + 二分图)
完全没往二分图这个方向想
树也是二分图,因为可以分层染色,使得节点分成两份,同一份之间没有边相连
直接分层黑白染色分成两份
首先保证同一份之间没有连边,可以在左边的点最高位全部为0,右边的点最高位为1次高位为0
然后给左边分配id,id位为0其他都为1,然后与其相连的右边点都id为1,其他为0
这样构造就刚好符合题目条件
这里注意需要id+2位,n等于100 只有60位
所以选点数小的做为左边的点
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
typedef long long ll;
const int N = 100 + 10;
int g[N][N], ans[N][N], n, cnt;
vector<int> v1, v2;
void dfs(int u, int fa, int d)
{
if(d % 2) v1.push_back(u);
else v2.push_back(u);
_for(v, 0, 100)
if(g[u][v])
{
if(v == fa) continue;
dfs(v, u, d + 1);
}
}
int main()
{
scanf("%d", &n);
_for(i, 1, n - 1)
{
int u, v;
scanf("%d%d", &u, &v);
g[u][v] = g[v][u] = 1;
}
dfs(1, 0, 0);
if(v1.size() > v2.size()) swap(v1, v2);
for(int u: v1)
{
_for(i, 1, 59) ans[u][i] = 1;
++cnt;
ans[u][cnt] = 0;
_for(v, 1, n)
if(g[u][v])
ans[v][cnt] = 1;
}
for(int v: v2) ans[v][60] = 1;
_for(i, 1, n)
{
ll x = 0;
for(int j = 60; j >= 1; j--)
x = x * 2 + ans[i][j];
printf("%lld ", x);
}
return 0;
}
E. Pchelyonok and Segments(dp)
其实不是特别难的dp
dp[i][j]表示后i个,最大长度为j的和的最大值
可以分i是不是成不成为一段来写转移方程
注意边界条件和细节
首先j=0时要赋成最大值,表示一定可以转移
j!=0时赋为最小值,表示不合法
注意是i = n + 1的时候
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
typedef long long ll;
const int N = 2e5 + 10;
const int M = 450;
int dp[N][M], n;
ll a[N], s[N];
int main()
{
int T; scanf("%d", &T);
while(T--)
{
scanf("%d", &n);
_for(i, 1, n)
{
scanf("%lld", &a[i]);
s[i] = s[i - 1] + a[i];
}
int k = 1;
while((k + 1) * (k + 2) / 2 <= n) k++;
_for(j, 1, k) dp[n + 1][j] = 0;
dp[n + 1][0] = 2e9;
for(int i = n; i >= 1; i--)
_for(j, 0, k)
{
dp[i][j] = dp[i + 1][j];
if(j && i + j - 1 <= n && s[i + j - 1] - s[i - 1] < dp[i + j][j - 1])
dp[i][j] = max((ll)dp[i][j], s[i + j - 1] - s[i - 1]);
}
for(int i = k; i >= 1; i--)
if(dp[1][i] > 0)
{
printf("%d\n", i);
break;
}
}
return 0;
}
D. Red-Green Towers(dp统计方案数)
同一题在不同的题单遇到
背包统计方案数
然后根据上下界统计答案就好了
注意下界要大于等于0,因为可能算出负值
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 2e5 + 10;
const int mod = 1e9 + 7;
int dp[N], r, g, h;
int main()
{
scanf("%d%d", &r, &g);
while((h + 1) * (h + 2) / 2 <= r + g) h++;
dp[0] = 1;
_for(i, 1, h)
for(int j = r; j >= i; j--)
dp[j] = (dp[j] + dp[j - i]) % mod;
int ans = 0;
_for(i, max(h * (h + 1) / 2 - g, 0), r)
ans = (ans + dp[i]) % mod;
printf("%d\n", ans);
return 0;
}
E. Number of Simple Paths(思维)
如果是树的话,两两之间只有一条路径,很容易统计
然后加了一条边,我想统计因为这条边而增加的路径,也就是经过这条边的路径,发现非常复杂,很难统计,就不知道了
看了题解,一颗树加一条边就是基环树
可以看成一个环,然后环上的一些节点挂着一颗树
这个时候发现很多两点之间都有两条路径
发现只有在一颗树上才是两两之间一条路径,所以统计树的节点个数减去就好了
实现上,无向图找环就用类似拓扑排序,度数为1就加入队列,最后没加入队列的点就是环上的点
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
typedef long long ll;
const int N = 2e5 + 10;
vector<int> g[N];
int degree[N], vis[N], n, cnt;
void dfs(int u, int fa)
{
cnt++;
for(int v: g[u])
{
if(v == fa || !vis[v]) continue;
dfs(v, u);
}
}
int main()
{
int T; scanf("%d", &T);
while(T--)
{
scanf("%d", &n);
_for(i, 1, n) g[i].clear(), vis[i] = 0, degree[i] = 0;
_for(i, 1, n)
{
int u, v;
scanf("%d%d", &u, &v);
g[u].push_back(v);
g[v].push_back(u);
degree[u]++; degree[v]++;
}
queue<int> q;
_for(i, 1, n)
if(degree[i] == 1)
q.push(i);
while(!q.empty())
{
int u = q.front(); q.pop();
vis[u] = 1;
for(int v: g[u])
if(--degree[v] == 1)
q.push(v);
}
ll res = 1LL * n * (n - 1);
_for(i, 1, n)
if(!vis[i])
{
cnt = 0;
dfs(i, 0);
res -= 1LL * cnt * (cnt - 1) / 2;
}
printf("%lld\n", res);
}
return 0;
}
周三
E. Compress Words(kmp)
字符串匹配用kmp
每次用kmp匹配最长的后缀等于前缀就行了
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 1e6 + 10;
int Next[N], lens, lena, n;
string s, ans;
void get_next()
{
Next[0] = -1;
int i = 0, j = -1;
lens = s.size();
while(i < lens)
{
if(j == -1 || s[i] == s[j])
{
i++; j++;
Next[i] = j;
}
else j = Next[j];
}
}
int kmp()
{
int j = 0, i = max(lena - lens, 0);
while(i < lena)
{
if(j == -1 || ans[i] == s[j]) i++, j++;
else j = Next[j];
}
return j;
}
int main()
{
scanf("%d", &n);
cin >> ans;
lena = ans.size();
_for(i, 2, n)
{
cin >> s;
get_next();
int j = kmp();
ans += string(s, j);
lena += lens - j;
}
cout << ans << endl;
return 0;
}
D. Book of Evil(树的直径)
对树的直径不熟导致没做出来
有一个很重要的性质,离一个点距离最远的点一定是树的直径的端点之一
因此求树的直径可以随便找一个点,找最远点,那就是端点之一,然后再以那个端点为起点就可以找到另一个端点
这道题,其实就是求特殊点的直径
每一个点离它最远的特殊点一定是直径之一
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 1e5 + 10;
int d1[N], d2[N], p[N], k[N], n, m, maxd, s1, s2, t;
vector<int> g[N];
void dfs(int u, int fa, int& s, int* d)
{
d[u] = d[fa] + 1;
if(p[u] && d[u] > d[s]) s = u;
for(int v: g[u])
{
if(v == fa) continue;
dfs(v, u, s, d);
}
}
int main()
{
scanf("%d%d%d", &n, &m, &maxd);
_for(i, 1, m)
{
int x; scanf("%d", &x);
p[x] = 1;
}
_for(i, 1, n - 1)
{
int u, v;
scanf("%d%d", &u, &v);
g[u].push_back(v);
g[v].push_back(u);
}
dfs(1, 0, s1, k);
dfs(s1, 0, s2, d1);
dfs(s2, 0, t, d2);
int ans = 0;
_for(i, 1, n)
if(d1[i] - 1 <= maxd && d2[i] - 1 <= maxd)
ans++;
printf("%d\n", ans);
return 0;
}
周日
Simone and Graph Coloring(贪心 + 线段树)
首先是贪心,每次选能选的最小的
然后可以发现,与其相连的一定是比它大的,因此这些的颜色一定是连续的
所以用线段树维护区间最大值最小值就好了
写的时候有细节需要注意
1.查询操作的时候有时候会出现 R > L的情况,这种情况要考虑怎么处理
2.统计答案的时候,若第一个先赋值了,那么ans就要赋第一个对应的答案。
#include <bits/stdc++.h>
#define l(k) (k << 1)
#define r(k) (k << 1 | 1)
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 1e6 + 10;
int t[N << 4][2];
int a[N], c[N], ans, n;
void up(int k)
{
t[k][0] = max(t[l(k)][0], t[r(k)][0]);
t[k][1] = min(t[l(k)][1], t[r(k)][1]);
}
void build(int k, int l, int r)
{
if(l == r)
{
t[k][1] = 1e9;
t[k][0] = 0;
return;
}
int m = l + r >> 1;
build(l(k), l, m);
build(r(k), m + 1, r);
up(k);
}
void add(int k, int l, int r, int x, int p)
{
if(l == r)
{
t[k][0] = t[k][1] = p;
return;
}
int m = l + r >> 1;
if(x <= m) add(l(k), l, m, x, p);
else add(r(k), m + 1, r, x, p);
up(k);
}
int query0(int k, int l, int r, int L, int R)
{
if(L <= l && r <= R) return t[k][0];
int m = l + r >> 1, res = 0;
if(L <= m) res = max(res, query0(l(k), l, m, L, R));
if(R > m) res = max(res, query0(r(k), m + 1, r, L, R));
return res;
}
int query1(int k, int l, int r, int L, int R)
{
if(L <= l && r <= R) return t[k][1];
int m = l + r >> 1, res = 1e9;
if(L <= m) res = min(res, query1(l(k), l, m, L, R));
if(R > m) res = min(res, query1(r(k), m + 1, r, L, R));
return res;
}
int main()
{
int T; scanf("%d", &T);
while(T--)
{
scanf("%d", &n);
_for(i, 1, n) scanf("%d", &a[i]);
build(1, 1, n);
c[1] = 1; ans = 1; //ans = 1关键!!
add(1, 1, n, a[1], c[1]);
_for(i, 2, n)
{
if(a[i] == n) c[i] = 1; // !!
else
{
int mx = query0(1, 1, n, a[i] + 1, n);
int mi = query1(1, 1, n, a[i] + 1, n);
if(mi > 1) c[i] = 1;
else c[i] = mx + 1;
}
add(1, 1, n, a[i], c[i]);
ans = max(ans, c[i]);
}
printf("%d\n", ans);
_for(i, 1, n) printf("%d ", c[i]); puts("");
}
return 0;
}
Parallel Sort(思维)
首先,我们可以让一个数到它应该去的位置连一个有向边
这样就可以形成很多个不相交的环
一个位置有一条入边,一条出边,如果环相交就不满足这个条件
那么其实就是要看一个环怎么处理
这个挺巧妙的,是队友想到了
先一次对称变换,然后就可以两两交换了
画个图就知道了
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 1e5 + 10;
int g[N], vis[N], cnt, n;
vector<int> c[N];
void dfs(int u, int id)
{
if(vis[u]) return;
vis[u] = 1;
c[id].push_back(u);
dfs(g[u], id);
}
int main()
{
scanf("%d", &n);
_for(i, 1, n)
{
int x; scanf("%d", &x);
g[x] = i;
}
int ok = 0;
_for(i, 1, n)
{
if(g[i] == i) continue;
if(!vis[i]) dfs(i, ++cnt);
ok = 1;
}
if(!ok)
{
puts("0");
return 0;
}
vector<int> pr[2];
_for(i, 1, cnt)
{
vector<int> t = c[i];
int l = 0, r = t.size() - 1;
while(l < r)
{
pr[0].push_back(t[l]);
pr[0].push_back(t[r]);
l++; r--;
}
l = 0, r = t.size() - 2;
while(l < r)
{
pr[1].push_back(t[l]);
pr[1].push_back(t[r]);
l++; r--;
}
}
if(pr[1].size()) printf("%d\n", 2);
else printf("%d\n", 1);
printf("%d", pr[0].size() / 2);
for(int x: pr[0]) printf(" %d", x); puts("");
if(pr[1].size())
{
printf("%d", pr[1].size() / 2);
for(int x: pr[1]) printf(" %d", x); puts("");
}
return 0;
}