做题记录#2
Team Building
来源:CF1316E, 2300
直接按照 $\mathrm{a[i]}$ 排序,然后发现决策点是连续的(除非观众选择完)
然后就直接令 $\mathrm{f[i][j]}$ 表示考虑前 $\mathrm{i}$ 个,状态为 $\mathrm{j}$ 的情况.
判断的话就用 $\mathrm{i-si[j]}$ 是否小于 $\mathrm{k}$ 来选观众即可.
#include <cstdio>
#include <vector>
#include <cstring>
#include <algorithm>
#define N 100009
#define ll long long
#define pb push_back
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
int g[N][9];
ll f[2][1 << 8];
ll h[2][8][1 << 8];
int n, p, k, a[N], id[N], si[1 << 8];
bool cmp(int i, int j) {
return a[i] > a[j];
}
int lowbit(int x) {
return x & (-x);
}
void init() {
si[0] = 0;
for(int i = 1; i < (1 << 8) ; ++ i)
si[i] = si[i - lowbit(i)] + 1;
}
int main() {
// setIO("input");
init();
scanf("%d%d%d", &n, &p, &k);
for(int i = 1; i <= n ; ++ i) {
scanf("%d", &a[i]);
}
for(int i = 1; i <= n ; ++ i) {
id[i] = i;
}
for(int i = 1; i <= n ; ++ i) {
for(int j = 1,x; j <= p ; ++ j) {
scanf("%d", &g[i][j - 1]);
}
}
sort(id + 1, id + 1 + n, cmp);
memset(f, -0x3f, sizeof(f));
int d = 0;
f[0][0] = 0;
for(int i = 0; i < n ; ++ i) {
memset(f[d ^ 1], -0x3f, sizeof(f[d ^ 1]));
for(int j = 0; j < (1 << p) ; ++ j) {
// f[i][j] -> f[i + 1][j] ?
f[d ^ 1][j] = max(f[d ^ 1][j], f[d][j]);
if(i - si[j] < k) {
f[d ^ 1][j] = max(f[d ^ 1][j], f[d][j] + a[id[i + 1]]);
}
for(int l = 0; l < p ; ++ l) {
int v = g[id[i + 1]][l];
if(j & (1 << l)) continue;
// printf("%d %d\n", j, l);
f[d ^ 1][j | (1 << l)] = max(f[d ^ 1][j | (1 << l)], f[d][j] + v);
}
}
d ^= 1;
}
printf("%lld", f[d][(1 << p) - 1]);
return 0;
}
Garden of the Sun
来源:CF1495C, 2300
一道构造题.
仔细读题,然后发现 $\mathrm{X}$ 点之间无公共点,也同样没有公共边.
利用这个性质的话可以对 $1, 4, 7..$ 列填满,然后其他列只考虑前两行.
这样既能保证覆盖所有 $\mathrm{X}$ 点,又能保证无环且联通.
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
#define N 504
#define ll long long
#define pb push_back
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
int n , m;
char a[N][N], fin[N][N];
void solve() {
scanf("%d%d", &n, &m);
for(int i = 1; i <= n ; ++ i) {
scanf("%s", a[i] + 1);
}
if(n == 1 || m == 1) {
for(int i = 1; i <= n ; ++ i) {
for(int j = 1; j <= m ; ++ j) {
printf("X");
}
if(i != n) printf("\n");
}
}
else {
for(int i = 1; i <= n ; ++ i) for(int j = 1; j <= m ; ++ j) fin[i][j] = a[i][j];
int p = (m - 1) % 3;
for(int i = min(2, p + 1); i <= m ; i += 3) {
for(int j = 1; j <= n ; ++ j) fin[j][i] = 'X';
if(i + 3 <= m) {
if(a[2][i + 1] != 'X' && a[2][i + 2] != 'X') {
fin[1][i + 1] = fin[1][i + 2] = 'X';
}
else {
fin[2][i + 1] = fin[2][i + 2] = 'X';
}
}
}
for(int i = 1; i <= n ; ++ i) {
for(int j = 1; j <= m ; ++ j) printf("%c", fin[i][j]);
if(i != n) printf("\n");
}
}
}
int main() {
// setIO("input");
int T;
scanf("%d", &T);
for(int i = 1; i <= T; ++ i) {
solve();
if(i != T) printf("\n");
}
return 0;
}
Great Vova Wall (Version 2)
来源:CF1092D2, 2200
对于一段相等的数字构成的连通块,若想集体加一,要满足长度为偶数.
不妨从小到大去枚举数字,那么每次小于等于 $\mathrm{i}$ 的数字构成的连通块大小必为偶数.
然后这个东西可以用并查集来维护,时间复杂度是 $\mathrm{O(n \log n)}$.
#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 200009
#define ll long long
#define pb push_back
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
int flag;
int a[N], u[N], fa[N], sz[N], c[3], n;
bool cmp(int i, int j) {
return a[i] < a[j];
}
int find(int x) {
return fa[x] == x ? x : fa[x] = find(fa[x]);
}
void merge(int x, int y) {
x = find(x), y = find(y);
c[sz[x] % 2] -- ;
c[sz[y] % 2] -- ;
if(sz[x] > sz[y]) {
swap(x, y);
}
fa[x] = y, sz[y] += sz[x];
c[sz[y] % 2] ++ ;
}
void solve(int l, int r) {
for(int i = l; i <= r; ++ i) {
int cur = u[i];
fa[cur] = cur;
sz[cur] = 1, c[1] ++ ;
if(fa[cur - 1]) {
merge(cur - 1, cur);
}
if(fa[cur + 1]) {
merge(cur + 1, cur);
}
}
if(c[1]) {
flag = 0;
}
}
int main() {
// setIO("input");
scanf("%d",&n);
flag = 1;
for(int i = 1; i <= n ; ++ i) {
scanf("%d", &a[i]);
u[i] = i;
}
sort(u + 1,u + 1 + n, cmp);
int pre = 1;
for(int i = 2; i <= n ; ++ i) {
if(a[u[i]] != a[u[i - 1]]) {
// 当前和前面不相等.
solve(pre, i - 1), pre = i;
}
}
if(flag) {
printf("YES");
}
else {
printf("NO");
}
return 0;
}
Kill Anton
来源:CF1526D, 2200
这道题需要大胆猜测结论.
这道题的结论就是答案的最优形式一定是相同字符连在一起的.
手画一下发现这样排布一定不会更劣,而相同的排在一起可以尽可能创造更多逆序对.
所以直接全排列枚举所有排布方式,然后提前预处理出数组求解逆序对即可.
#include <cstdio>
#include <vector>
#include <cstring>
#include <algorithm>
#define ll long long
#define pb push_back
#define N 100009
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
char str[N];
int n, c[5], arr[5], an[5];
ll num[5][5], best;
int ID(int c) {
if(c + 'A' == 'A') return 0;
if(c + 'A' == 'N') return 1;
if(c + 'A' == 'O') return 2;
if(c + 'A' == 'T') return 3;
}
void check() {
ll tp = 0ll;
for(int i = 1; i <= 4; ++ i) {
for(int j = 1; j < i; ++ j) {
tp += num[arr[i]][arr[j]];
}
}
if(tp >= best) {
best = tp;
for(int i = 1; i <= 4; ++ i) {
an[i] = arr[i];
}
}
}
void solve() {
scanf("%s", str + 1);
n = strlen(str + 1);
memset(c, 0, sizeof(c));
memset(num , 0, sizeof(num));
best = 0ll;
for(int i = 1; i <= n ; ++ i) {
int p = ID(str[i] - 'A');
for(int j = 0; j < 4; ++ j) {
num[j][p] += 1ll * c[j];
}
++c[p];
}
for(int i = 1; i <= 4; ++ i) arr[i] = i - 1;
do {
check();
}while(next_permutation(arr + 1, arr + 1 + 4));
// printf("%lld\n", best);
for(int i = 1; i <= 4; ++ i) {
for(int j = 1; j <= c[an[i]]; ++ j) {
if(an[i] == 0) {
printf("A");
}
if(an[i] == 1) {
printf("N");
}
if(an[i] == 2) {
printf("O");
}
if(an[i] == 3) {
printf("T");
}
}
}
printf("\n");
}
int main() {
// setIO("input");
int T;
scanf("%d", &T);
while(T--) solve();
return 0;
}
Minimal Segment Cover
来源:CF1175E, 2200
直接设置状态 $\mathrm{f[i][j]}$ 表示从 $\mathrm{i}$ 点开始走 $\mathrm{2^j}$ 步最多到哪里.
这个直接用倍增求,然后询问的时候倍增跳即可.
#include <cstdio>
#include <vector>
#include <cstring>
#include <algorithm>
#define N 500009
#define ll long long
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
int n , m , L[N], R[N], f[N][20];
int main() {
// setIO("input");
scanf("%d%d", &n, &m);
int mx = 0;
for(int i = 1; i <= n ; ++ i) {
scanf("%d%d", &L[i], &R[i]);
mx = max(mx, R[i]);
f[L[i]][0] = max(f[L[i]][0], R[i]);
}
for(int i = 0 ; i <= mx ; ++ i) {
f[i][0] = max(f[i][0], max(i, f[i - 1][0]));
}
for(int i = mx; i >= 0; -- i) {
for(int j = 1; j < 20 ; ++ j) {
f[i][j] = f[f[i][j - 1]][j - 1];
}
}
for(int i = 1; i <= m ; ++ i) {
int x, y;
scanf("%d%d", &x, &y);
if(y > mx) {
printf("-1\n");
}
else {
int ans = 0;
for(int l = 19; l >= 0; -- l) {
if(f[x][l] < y) {
ans += 1 << l;
x = f[x][l];
}
}
x = f[x][0], ++ ans;
if(x < y || ans > n) {
printf("-1\n");
}
else {
printf("%d\n", ans);
}
}
}
return 0;
}
Minimal Labels
来源:CF825E, 2300
正着编号可能会出现不合法的情况,不妨倒着安排.
建立反向边,然后从编号大的开始分配.
显然,要优秀给编号大的分配大数字,然后再给小的分配.
用一个优先队列代替普通队列,然后每次取出队首的时候赋值并更新其他点.
这样,在最后就构成了合法的字典序最小的方案.
#include <cstdio>
#include <cstring>
#include <vector>
#include <queue>
#include <algorithm>
#define N 100009
#define ll long long
#define pb push_back
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
int n , m, deg[N], id[N], inq[N], sum ;
priority_queue<int>q;
vector<int>G[N];
int main() {
// setIO("input");
scanf("%d%d",&n,&m);
for(int i=1;i<=m;++i) {
int x, y;
scanf("%d%d", &x, &y);
G[y].pb(x);
++ deg[x];
}
sum = n;
for(int i = 1; i <= n ; ++ i) {
if(!deg[i]) {
q.push(i), inq[i] = 1;
}
}
while(!q.empty()) {
int u = q.top(); q.pop();
id[u] = sum -- ;
for(int i = 0; i < G[u].size() ; ++ i) {
int v = G[u][i];
-- deg[v];
if(!deg[v]) {
q.push(v);
}
}
}
for(int i = 1; i <= n ; ++ i) {
printf("%d ", id[i]);
}
return 0;
}
Returning Home
来源:CF1442D, 2300
加上起点和终点,则只需要考虑关键点.
直观上讲,可以 $\mathrm{O(m^2)}$ 时间暴力建图,然后跑一个 $\mathrm{dijkstra}$ 算法.
然而,这么做是没有必要的,因为我们发现,在最优策略下可以让每个点向横/纵坐标相邻点移动.
所以按照 $\mathrm{x, y}$ 分别排一下序然后建个图就行,边数是 $\mathrm{O(m)}$ 的.
#include <cstdio>
#include <queue>
#include <cstring>
#include <vector>
#include <algorithm>
#define N 200009
#define pb push_back
#define ll long long
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
int n,m,sx,sy,fx,fy;
ll d[N];
int vis[N];
struct data {
int u;
ll dis;
data(int u = 0, ll dis = 0):u(u),dis(dis){}
bool operator<(const data b) const {
return dis > b.dis;
}
};
priority_queue<data>q;
struct point {
int x, y, id;
}a[N];
bool cmp(point i, point j) {
return i.x < j.x;
}
bool cmp2(point i, point j) {
return i.y < j.y;
}
int hd[N], to[N<<1], nex[N<<1], val[N<<1], edges;
void add(int u,int v,int c) {
nex[++edges]=hd[u], hd[u]=edges;
to[edges]=v, val[edges]=c;
}
void dij() {
memset(d, 0x3f, sizeof(d));
d[0] = 0;
q.push(data(0, 0));
while(!q.empty()) {
data e = q.top(); q.pop();
if(vis[e.u] ) continue;
vis[e.u] = 1;
for(int i = hd[e.u]; i ; i = nex[i]) {
int v = to[i];
if(d[v] > e.dis + val[i]){
d[v] = e.dis + val[i];
q.push(data(v, d[v]));
}
}
}
}
int main() {
// setIO("input");
scanf("%d%d",&n,&m);
scanf("%d%d%d%d",&sx,&sy,&fx,&fy);
for(int i=1;i<=m;++i) {
scanf("%d%d",&a[i].x, &a[i].y);
a[i].id = i;
}
a[m + 1].x = fx;
a[m + 1].y = fy;
for(int i = 1; i <= m ; ++ i) {
add(0, i, min(abs(sx - a[i].x), abs(sy - a[i].y)));
// add(i, 0, abs(sx - a[i].x) + abs(sy - a[i].y));
add(i, m + 1, abs(fx - a[i].x) + abs(fy - a[i].y));
}
add(0, m + 1, abs(sx - fx) + abs(sy - fy));
// 构建完毕,然后开始建图.
sort(a + 1, a + 1 + m, cmp);
for(int i = 1; i < m ; ++ i) {
add(a[i].id, a[i + 1].id, min(abs(a[i].x - a[i + 1].x), abs(a[i].y - a[i + 1].y)));
add(a[i + 1].id, a[i].id, min(abs(a[i].x - a[i + 1].x), abs(a[i].y - a[i + 1].y)));
}
sort(a + 1, a + 1 + m, cmp2);
for(int i = 1; i < m ; ++ i) {
add(a[i].id, a[i + 1].id, min(abs(a[i].x - a[i + 1].x), abs(a[i].y - a[i + 1].y)));
add(a[i + 1].id, a[i].id, min(abs(a[i].x - a[i + 1].x), abs(a[i].y - a[i + 1].y)));
}
// 建立完毕所有边了.
dij();
printf("%lld\n", d[m + 1]);
return 0;
}
Winter is here
来源:CF839D, 2200
计数题.
第一反应是拆成每个数分别求贡献,但是没什么好的思路.
而正解反而就是最直接的方法,令 $\mathrm{dp[i]}$ 表示由 $\mathrm{i}$ 的倍数构成的长度总和.
如果能求出 $\mathrm{dp[i]}$, 则可以容斥求出恰为 $\mathrm{i}$ 的方案数.
然后会发现求 $\mathrm{dp[i]}$ 的式子化简一下直接可以 $O(1)$ 来计算.
#include <cstdio>
#include <cstring>
#include <vector>
#include <algorithm>
#define N 200009
#define M 1000009
#define ll long long
#define pb push_back
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
const int mod = 1000000007;
int n , cur[1000003], dp[1000003], bu[M], a[N], bin[M];
int main() {
// setIO("input");
scanf("%d",&n);
for(int i=1;i<=n;++i) {
scanf("%d", &a[i]);
bu[a[i]] ++ ;
}
for(int i = 2; i <= 1000000; ++ i) {
for(int j = i; j <= 1000000; j += i) {
cur[i] += bu[j];
}
}
bin[0] = 1;
for(int i = 1; i < M ; ++ i) bin[i] = (ll)bin[i - 1] * 2 % mod;
int ans = 0;
for(int i = 1000000; i >= 2; -- i) {
if(!cur[i]) continue;
dp[i] = (ll)cur[i] * bin[cur[i] - 1] % mod;
for(int j = i + i; j <= 1000000; j += i) {
dp[i] = (ll)(dp[i] - dp[j] + mod) % mod;
}
(ans += (ll)dp[i] * i % mod) %= mod;
}
printf("%d\n", ans);
return 0;
}
Phoenix and Computers
来源:CF1515E, 2200
计数题.
开始设的状态是 $\mathrm{f[i][j]}$ 表示开完了 $\mathrm{i}$ 个元素,花了 $\mathrm{j}$ 步的方案数.
但是这个状态转移是 $\mathrm{O(n^4)}$ 的,无法通过.
复杂度如此之高的瓶颈就是既需要枚举最后操作位置,还需要枚举左区间的步数.
如果可以强制左区间全部为人为操作,即操作数固定则问题可以得到很好的简化.
那么就枚举第一个自动操作的 $\mathrm{k}$,然后左右两边分别转移乘一下就好了.
#include <cstdio>
#include <vector>
#include <cstring>
#include <algorithm>
#define N 503
#define ll long long
#define pb push_back
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
int n, mod, C[N][N], f[N][N];
void init() {
C[0][0] = 1;
for(int i=1;i<N;++i) {
C[i][0] = 1;
for(int j = 1; j <= i ; ++ j) {
C[i][j] = (ll)(C[i - 1][j] + C[i - 1][j - 1]) % mod;
}
}
}
int main() {
// setIO("input");
scanf("%d%d",&n,&mod);
init();
f[1][1] = 1, f[2][2] = 2;
for(int i = 3; i <= n ; ++ i) {
for(int j = 1; j <= i; ++ j) {
(f[i][i] += C[i - 1][j - 1]) %= mod;
// 这个 j 枚举的是起点,即起点左侧和右侧有个顺序之分.
}
for(int j = 1; j < i; ++ j) {
for(int k = 2; k <= min(j, i - 1); ++ k) {
// 枚举第一个自动填充的位置
(f[i][j] += (ll)f[k - 1][k - 1] * f[i - k][j - k + 1] % mod * C[j][k - 1] % mod) %= mod;
}
}
}
int ans = 0;
for(int i = 1; i <= n ; ++ i) {
(ans += f[n][i]) %= mod;
}
printf("%d\n", ans);
return 0;
}
Guess the K-th Zero (Hard version)
来源:CF1520F2, 2200
最直观的想法是来一波二分答案,但是显然通过不了.
而无论如何,二分的操作是不可避免的,均摊下来每次的二分次数大概要在 5 左右.
所以不妨考虑对序列分块,每个块的大小设为 $\mathrm{B}$, 在最开始求出块中 0 的个数.
然后每次询问的时候用树状数组+二分找到当前答案属于哪个块,在小块里再二分找一下.
这样的总查询复杂度是 $O(\mathrm{\frac{n}{B} + 10^4 \log B})$, $B=20$ 左右比较好.
#include <cstdio>
#include <iostream>
#include <vector>
#include <cstring>
#include <algorithm>
#define N 15000
#define ll long long
#define pb push_back
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
struct BIT {
int C[N];
int lowbit(int x) {
return x & (-x);
}
void upd(int x, int v) {
while(x < N) {
C[x] += v;
x += lowbit(x);
}
}
int query(int x) {
int tmp = 0;
while(x > 0) {
tmp += C[x];
x -= lowbit(x);
}
return tmp;
}
}T;
int n, Q, B, cnt;
struct data {
int l, r, v;
data(int l = 0, int r = 0, int v = 0):l(l), r(r), v(v){}
}A[N];
int Query(int l, int r) {
printf("? %d %d\n", l, r);
printf("\n");
fflush(stdout);
int x;
scanf("%d", &x);
return r - l + 1 - x;
}
void init() {
B = 20;
for(int i = 1; i <= n ; i += B) {
int l = i, r = min(i + B - 1, n);
// 构建[l, r]
A[++ cnt] = data(l, r, Query(l, r));
}
for(int i = 1; i <= cnt ; ++ i) {
T.upd(i, A[i].v);
}
}
void find(int x) {
printf("! %d\n", x);
printf("\n");
fflush(stdout);
}
void solve(int id, int cur) {
int L = A[id].l, R = A[id].r;
int l = L, r = R, mid, an = 0;
while(l <= r) {
mid = (l + r) >> 1;
if(Query(L, mid) >= cur) {
an = mid, r = mid - 1;
}
else {
l = mid + 1;
}
}
find(an);
A[id].v -- ;
T.upd(id, -1);
}
int main() {
// setIO("input");
scanf("%d%d",&n,&Q);
for(int i = 1; i <= Q; ++ i) {
int k;
scanf("%d", &k);
if(i == 1) {
init();
}
int l = 1, r = cnt, mid, id = 0;
while(l <= r) {
mid = (l + r) >> 1;
if(T.query(mid) >= k) {
id = mid, r = mid - 1;
}
else {
l = mid + 1;
}
}
int pre = T.query(id - 1);
int cur = k - pre;
solve(id, cur);
}
return 0;
}
Adam and Tree
来源:CF442D, 2600
很神的一道题.
假设树已经建出来,考虑如何求全局最小的最大值:
令 $\mathrm{f[x]}$ 表示 $\mathrm{x}$ 子树的最大值,然后 $\mathrm{m1[x], m2[x]}$ 分别表示儿子的最大/次大.
因为我们只希望最大值最小,所以在对 $\mathrm{x}$ 至 $\mathrm{fa[x]}$ 染色时显然要按照儿子的最大值染.
那么就有 $\mathrm{f[x]=max(m1[x], m2[x]+1})$.
在进行 $\mathrm{DP}$ 时,对于每一个 $\mathrm{x}$ 点都取了最优状态,构成了全局的最优解.
那么考虑新加入一个点会构成什么影响:
显然,只会影响 $\mathrm{x}$ 到根的这条链,然后就可以依次向上更新.
显然,每个点的答案肯定优于树剖的答案,即 $O(\log n)$, 故发现未更新时跳出即可.
#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 1000009
#define ll long long
#define pb push_back
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
int f[N], m1[N], m2[N], fa[N];
int main() {
// setIO("input");
int n ;
scanf("%d",&n);
for(int i = 2; i <= n + 1; ++ i) {
f[i] = 1;
scanf("%d", &fa[i]);
for(int t = i; ; t = fa[t]) {
if(f[t] > m1[fa[t]]) {
m2[fa[t]] = m1[fa[t]];
m1[fa[t]] = f[t];
}
else m2[fa[t]] = max(m2[fa[t]], f[t]);
if(max(m1[fa[t]], m2[fa[t]] + 1) == f[fa[t]]) break;
else f[fa[t]] = max(m1[fa[t]], m2[fa[t]] + 1);
}
printf("%d ", m1[1]);
}
return 0;
}
Three States
来源:CF590C, 2200
这道题的思路非常简单,主要是联系一下 0/1 bfs.
如果边权可能为 0, 也可能为 1,则用双端队列来扩展.
扩展 0 边时放到队首,扩展 1 边时放到队尾.
在 $\mathrm{bfs}$ 中,一定是距离小的先入队.
然后考虑加入新点时能否依然满足此性质:
若为 0 边,则和当前所扩展点距离相同,显然还是最小值.
若为 1 边,则根据递归定义,为当前的最大值,所以放到后面.
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <queue>
#define N 1009
#define pb push_back
#define ll long long
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
const int inf = (int)1e8;
int dx[4] = {-1, 0, 1, 0};
int dy[4] = {0, -1, 0, 1};
int dis[4][N][N], n, m;
char str[N][N];
struct data {
int x, y;
data(int x=0, int y=0):x(x),y(y){}
};
deque<data>q;
void bfs(int d) {
for(int i=1;i<=n;++i) {
for(int j=1;j<=m;++j) {
if(str[i][j]>='1'&&str[i][j]<='3'&&str[i][j]-'0'==d) {
q.push_front(data(j, i));
dis[d][i][j] = 0;
}
}
}
while(!q.empty()) {
data e = q.front(); q.pop_front();
int x = e.x;
int y = e.y;
for(int i=0;i<4;++i) {
int xx = x + dx[i];
int yy = y + dy[i];
if(xx >= 1 && xx <= m && yy >= 1 && yy <= n && str[yy][xx] != '#') {
if(str[yy][xx] == '.') {
// cost = 1
if(dis[d][y][x] + 1 < dis[d][yy][xx]) {
dis[d][yy][xx] = dis[d][y][x] + 1;
q.push_back(data(xx, yy));
}
}
else {
if(dis[d][y][x] < dis[d][yy][xx]) {
dis[d][yy][xx] = dis[d][y][x];
q.push_front(data(xx, yy));
}
}
}
}
}
}
int main() {
// setIO("input");
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i) {
scanf("%s", str[i] + 1);
}
memset(dis, 0x3f, sizeof(dis));
bfs(1), bfs(2), bfs(3);
int ans = inf;
for(int i = 1; i <= n ; ++ i) {
for(int j = 1; j <= m ; ++ j) {
if(str[i][j] == '#') continue;
if(max(dis[1][i][j], max(dis[2][i][j], dis[3][i][j])) >= inf) continue;
int det = dis[1][i][j] + dis[2][i][j] + dis[3][i][j];
if(str[i][j] == '.') det -= 2;
ans = min(ans, det);
}
}
printf("%d\n", ans == inf ? -1 : ans);
return 0;
}
Rotating Substrings
来源:CF1363F, 2600
显然,如果每种字母在两个串中出现次数相同就肯定合法.
每一次操作相当于将 $\mathrm{r}$ 位置的字母删掉,加到 $\mathrm{l}$ 位置前面.
对于后缀来说,每次会删掉元素,而对于前缀就不好分析了,因为并不知道加在哪里.
不妨将字符串都取反,然后就是前缀删除,插到后缀里.
令 $\mathrm{f[i][j]}$ 表示 $\mathrm{s}$ 中的前 $\mathrm{i}$ 个最少操作几次才能和 $\mathrm{t}$ 中的前缀 $\mathrm{j}$ 匹配.
有 :
$\mathrm{f[i][j]=f[i-1][j-1]}$
$\mathrm{f[i][j]}=f[i][j-1]$
$\mathrm{f[i][j]=f[i-1][j]+1}$.
第一个好理解,然后后面两个的话表示删掉 $\mathrm{i}$, 预留给后面和直接用上前面预留的.
由于每次会判断 $\mathrm{(i,j)}$ 这两个前缀的字符个数是否会够用,所以一定能够配上.
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <queue>
#include <vector>
#define N 2009
#define ll long long
#define pb push_back
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
const int inf = 100000000;
int n ;
char ss[N], tt[N];
int ns[N][26], nt[N][26], f[N][N];
int check(int r1, int r2) {
for(int i = 0 ; i < 26 ; ++ i) {
if(ns[r1][i] < nt[r2][i]) return 0;
}
return 1;
}
void solve() {
int n ;
scanf("%d",&n);
scanf("%s",ss+1);
scanf("%s",tt+1);
for(int i=1;i<=n/2;++i) {
swap(ss[i], ss[n - i + 1]);
swap(tt[i], tt[n - i + 1]);
}
for(int i=1;i<=n;++i) {
for(int j=0;j<26;++j) {
ns[i][j] = ns[i - 1][j];
nt[i][j] = nt[i - 1][j];
}
ns[i][ss[i] - 'a'] ++ ;
nt[i][tt[i] - 'a'] ++ ;
}
int flag = 0;
for(int i = 0 ; i < 26 ; ++ i) {
if(ns[n][i] != nt[n][i]) {
flag = 1;
break;
}
}
if(flag) {
printf("-1\n");
}
else {
for(int i = 0; i <= n ; ++ i) {
for(int j = 0; j <= n ; ++ j) f[i][j] = inf;
}
f[0][0] = 0;
for(int i = 1; i <= n ; ++ i) {
for(int j = 0; j <= i ; ++ j) {
if(j && !check(i, j)) continue;
f[i][j] = f[i - 1][j] + 1;
if(j) f[i][j] = min(f[i][j], f[i][j - 1]);
if(j && ss[i] == tt[j]) {
f[i][j] = min(f[i][j], f[i - 1][j - 1]);
}
}
}
printf("%d\n", f[n][n]);
}
for(int i = 0 ; i <= n ; ++ i) {
for(int j = 0; j < 26 ; ++ j) {
ns[i][j] = nt[i][j] = 0;
}
}
}
int main() {
// setIO("input");
int T;
scanf("%d",&T);
while(T--) solve();
return 0;
}
Paint
来源:CF1572C, 2600
直观上应该直接用上区间 $\mathrm{DP}$.
令 $\mathrm{f[i][j][k]}$ 表示将 $\mathrm{[i,j]}$ 染成 $\mathrm{k}$ 的最小步数.
不过这么做的复杂度比较高.
然而最后只要求所有颜色相同,并不规定哪种颜色,所以可以将区间染成右端颜色.
这么做是正确的,因为如果染成的颜色不是右端点,则右端点染成新颜色还要花更多代价.
那莫不如令 $\mathrm{[i,j-1]}$ 染成一种颜色,最后再染成右端点.
然后这个 $\mathrm{DP}$ 的状态数量就是 $\mathrm{O(n^2)}$, 转移就是 $\mathrm{O(20)}$ 了.
#include <cstdio>
#include <vector>
#include <cstring>
#include <algorithm>
#define N 3009
#define ll long long
#define pb push_back
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
int n,m,a[N],c[N],pre[N],pos[N],f[N][N];
void solve() {
scanf("%d",&n);
for(int i=1;i<=n;++i) {
scanf("%d",&a[i]);
}
for(int i=1;i<=n;++i) {
if(a[i]!=a[i-1]) c[++m]=a[i];
}
// [1, m];
for(int i=1;i<=m;++i) {
pre[i]=pos[c[i]];
pos[c[i]]=i;
}
for(int i=1;i<=m;++i) {
for(int j=i;j<=m;++j) f[i][j]=j-i;
}
for(int len=1;len<=m;++len) {
for(int l=1;l+len-1<=m;++l) {
int r=l+len-1;
if(l==r) {
f[l][r] = 0;
continue;
}
f[l][r]=min(f[l+1][r], f[l][r-1])+1;
for(int p=pre[r];p>=l;p=pre[p]) {
f[l][r]=min(f[l][r], f[l][p]+f[p+1][r]);
}
}
}
printf("%d\n",f[1][m]);
for(int i=1;i<=m;++i) {
pre[i]=pos[c[i]]=0;
c[i]=0;
for(int j=1;j<=m;++j) f[i][j]=0;
}
n=m=0;
}
int main() {
// setIO("input");
int T;
scanf("%d",&T);
while(T--) solve();
return 0;
}
Kuroni and the Score Distribution
来源:CF1305E, 2200
先考虑如何构造最多的三元组:
对于一个位置 $\mathrm{j}$ 来说,显然最多在前面只有一个 $\mathrm{i}$ 与其配对.
所以直接让序列变为 $1,2,3,.....$ 会构成最多的三元组.
每个位置的贡献为 $\frac{i-1}{2}$, 向下取整.
如果 $\mathrm{m}$ 小于最大值则无解.
否则构建多的部分可以通过增加最后一个元素来消掉,每增加 $2$ 就减小 $1$.
然后后面的无关元素利用一些非常大的数字填充就好了.
#include <cstdio>
#include <vector>
#include <cstring>
#include <algorithm>
#define N 5009
#define ll long long
#define pb push_back
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
int a[N];
int main() {
// setIO("input");
int n,m,sum=0;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i) {
a[i] = i;
sum += (a[i] - 1) / 2;
if(sum > m) {
a[i] += (sum - m) * 2;
for(int j = n; j > i; -- j) {
a[j] = (int)1e9 - (n - j + 1) * 10000;
}
for(int j=1;j<=n;++j) {
printf("%d ", a[j]);
}
return 0;
}
}
if(sum == m) {
for(int i=1;i<=n;++i) printf("%d ", i);
}
else printf("-1\n");
return 0;
}
Trips
来源:CF1037E, 2200
考虑给定这个图时的做法:
把度数不满足的放进队列里然后暴力拓展.
加边不好搞,但可以删边, 然后每次加进不合法的点.
每个点只有被删时才会拓展,复杂度为线性.
#include <cstdio>
#include <vector>
#include <queue>
#include <cstring>
#include <algorithm>
#define N 200009
#define ll long long
#define pb push_back
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
int n,m,K,deg[N],tot;
queue<int>Q;
int U[N], V[N], mark[N], ans[N], vis[N << 1];
int hd[N], to[N << 1], nex[N << 1], val[N << 1],edges;
void add(int u, int v, int c) {
nex[++edges]=hd[u];
hd[u]=edges;
to[edges]=v;
val[edges]=c;
}
int main() {
// setIO("input");
scanf("%d%d%d",&n,&m,&K);
tot = n;
for(int i=1;i<=m;++i) {
int x, y;
scanf("%d%d",&x,&y);
add(x, y, i);
add(y, x, i);
U[i] = x;
V[i] = y;
++deg[x];
++deg[y];
}
for(int i=1;i<=n;++i) if(deg[i] < K) {
mark[i] = 1;
Q.push(i);
-- tot;
}
while(!Q.empty()) {
int u=Q.front(); Q.pop();
for(int i=hd[u];i;i=nex[i]) {
if(vis[val[i]] || mark[to[i]]) continue;
vis[val[i]] = 1;
int v=to[i];
--deg[v];
if(deg[v] < K && !mark[v]) {
Q.push(v);
mark[v] = 1;
-- tot;
}
}
}
ans[m] = tot;
for(int i=m;i>1;--i) {
// 枚举 i 操作前的答案.
int u = U[i];
int v = V[i];
if(!vis[i]) {
-- deg[u];
-- deg[v];
if(deg[u] < K && !mark[u]) {
mark[u] = 1;
Q.push(u);
-- tot;
}
if(deg[v] < K && !mark[v]) {
mark[v] = 1;
Q.push(v);
-- tot;
}
vis[i] = 1;
while(!Q.empty()) {
int x=Q.front(); Q.pop();
for(int j=hd[x];j;j=nex[j]) {
int y = to[j];
if(val[j] >= i || vis[val[j]] || mark[y]) continue;
// if(i == 6) printf("%d %d\n", v, deg[v]);
vis[val[j]] = 1;
-- deg[y];
if(deg[y] < K && !mark[y]) {
mark[y] = 1;
Q.push(y);
-- tot;
}
}
}
}
ans[i - 1] = tot;
}
for(int i=1;i<=m;++i) {
printf("%d\n", ans[i]);
}
return 0;
}
Almost Acyclic Graph
来源:CF915D, 2200
这道题有一个非常神的做法:
考虑对于一个图如何判断是否有环:跑一遍拓朴排序,然后看是否有点有度数.
考虑删掉一条边 $\mathrm{(x,y)}$ 对过程的影响:
$\mathrm{y}$ 的入度在开局就会自动减少 $1$, 而不必等到 $\mathrm{x}$ 去更新它.
所以,对于所有 $\mathrm{(x',y)}$ 的边,只需考虑 $\mathrm{y}$, 而不必考虑 $\mathrm{x}$.
这种影响只有 $\mathrm{O(n)}$ 种,每次通过 $\mathrm{O(n+m)}$ 的拓扑排序判断即可.
#include <cstdio>
#include <vector>
#include <cstring>
#include <queue>
#include <algorithm>
#define ll long long
#define N 504
#define M 100009
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
int n,m;
queue<int>q;
int hd[N],to[M],nex[M],deg[N],D[N],edges;
void add(int u,int v) {
nex[++edges]=hd[u];
hd[u]=edges;
to[edges]=v;
}
int topo() {
for(int i=1;i<=n;++i) {
deg[i] = D[i];
if(!deg[i]) {
q.push(i);
}
}
while(!q.empty()) {
int u=q.front(); q.pop();
for(int i=hd[u];i;i=nex[i]) {
int v=to[i];
--deg[v];
if(!deg[v]) q.push(v);
}
}
for(int i=1;i<=n;++i) {
if(deg[i]>0) return 0;
}
return 1;
}
int main() {
// setIO("input");
scanf("%d%d",&n,&m);
for(int i=1;i<=m;++i) {
int x,y;
scanf("%d%d",&x,&y);
add(x, y);
++D[y];
}
if(topo()) {
printf("YES\n");
}
else {
for(int i=1;i<=n;++i) {
if(D[i]) {
--D[i];
if(topo()) {
printf("YES\n");
return 0;
}
++D[i];
}
}
printf("NO\n");
}
return 0;
}
更直接的做法就是随便找出一个环,然后显然肯定要断环上的边.
这样枚举的复杂度也就降为 $\mathrm{O(n)}$ 了,同样能过.
#include <cstdio>
#include <vector>
#include <cstring>
#include <queue>
#include <algorithm>
#define ll long long
#define N 504
#define M 100009
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
int n,m;
queue<int>q;
int hd[N],to[M],nex[M],deg[N],D[N],edges;
void add(int u,int v) {
nex[++edges]=hd[u];
hd[u]=edges;
to[edges]=v;
}
int topo() {
for(int i=1;i<=n;++i) {
deg[i] = D[i];
if(!deg[i]) {
q.push(i);
}
}
while(!q.empty()) {
int u=q.front(); q.pop();
for(int i=hd[u];i;i=nex[i]) {
int v=to[i];
--deg[v];
if(!deg[v]) q.push(v);
}
}
for(int i=1;i<=n;++i) {
if(deg[i]>0) return 0;
}
return 1;
}
int main() {
// setIO("input");
scanf("%d%d",&n,&m);
for(int i=1;i<=m;++i) {
int x,y;
scanf("%d%d",&x,&y);
add(x, y);
++D[y];
}
if(topo()) {
printf("YES\n");
}
else {
for(int i=1;i<=n;++i) {
if(D[i]) {
--D[i];
if(topo()) {
printf("YES\n");
return 0;
}
++D[i];
}
}
printf("NO\n");
}
return 0;
}
Sausage Maximization
来源:CF282E, 2200
不就是裸的 01 - $\mathrm{trie}$ 嘛,不知道意义何在.
#include <cstdio>
#include <vector>
#include <cstring>
#include <queue>
#include <algorithm>
#define ll long long
#define N 504
#define M 100009
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
int n,m;
queue<int>q;
int hd[N],to[M],nex[M],deg[N],D[N],edges;
void add(int u,int v) {
nex[++edges]=hd[u];
hd[u]=edges;
to[edges]=v;
}
int topo() {
for(int i=1;i<=n;++i) {
deg[i] = D[i];
if(!deg[i]) {
q.push(i);
}
}
while(!q.empty()) {
int u=q.front(); q.pop();
for(int i=hd[u];i;i=nex[i]) {
int v=to[i];
--deg[v];
if(!deg[v]) q.push(v);
}
}
for(int i=1;i<=n;++i) {
if(deg[i]>0) return 0;
}
return 1;
}
int main() {
// setIO("input");
scanf("%d%d",&n,&m);
for(int i=1;i<=m;++i) {
int x,y;
scanf("%d%d",&x,&y);
add(x, y);
++D[y];
}
if(topo()) {
printf("YES\n");
}
else {
for(int i=1;i<=n;++i) {
if(D[i]) {
--D[i];
if(topo()) {
printf("YES\n");
return 0;
}
++D[i];
}
}
printf("NO\n");
}
return 0;
}
Break Up
来源:CF700C, 2600
可以枚举边,依次断开,然后判断是否联通.
若不连通,则可以跑一遍 $\mathrm{tarjan}$ 找到桥然后更新答案.
这样做的复杂度是 $\mathrm{O(m^2)}$ 的,可惜过不了.
分析一下性质,发现若提取出一条 $\mathrm{s}$ 到 $\mathrm{t}$ 的路径则这条路径的边肯定得删一条.
那么我们枚举的边数就降至 $O(n)$ 了,可以通过本题.
#include <cstdio>
#include <vector>
#include <cstring>
#include <algorithm>
#define ll long long
#define pb push_back
#define N 1009
#define M 300009
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
int n,m,os,ot;
int mk[M<<1],ed[N],dfn[N],low[N],scc,I,C, A, B, fin;
int hd[N], to[M << 1], nex[M << 1], val[M << 1], edges;
struct Edge {
int u,v,c;
Edge(int u=0,int v=0,int c=0):u(u),v(v),c(c){}
}e[M<<1];
void add(int u,int v,int c) {
nex[++edges]=hd[u];
hd[u]=edges;
to[edges]=v;
val[edges]=c;
}
// 只需要找到桥就行.
void tarjan(int x, int ff) {
low[x]=dfn[x]=++scc;
for(int i=hd[x];i;i=nex[i]) {
int v=to[i];
if((i ^ 1) == ff || mk[i]) continue; // 同一条边.
if(!dfn[v]) {
tarjan(v, i);
low[x] = min(low[x], low[v]);
if(low[v] > dfn[x] && dfn[ot] && dfn[ot] >= dfn[v] && dfn[ot] <= ed[v] && val[i] < C) {
C = val[i];
I = i;
}
}
else {
low[x] = min(low[x], dfn[v]);
}
}
ed[x] = scc;
// 可以看做 dfs 树上的 dfs 序.
}
void solve(int cur) {
I = 0, C = 1200000000;
mk[cur * 2] = mk[cur * 2 + 1] = 1;
tarjan(os, 0);
if(!dfn[ot]) {
if(e[cur].c < fin) {
fin = e[cur].c;
A = cur, B = 0;
}
}
else {
// 已经联通了,就看桥了.
if(I) {
if(C + e[cur].c < fin) {
fin = C + e[cur].c;
A = cur, B = (I >> 1);
}
}
}
mk[cur * 2] = mk[cur * 2 + 1] = 0;
for(int i = 1; i <= n ; ++ i) {
low[i] = dfn[i] = ed[i] = 0;
}
}
int vis[N], fa[N], fr[N];
void dfs(int x) {
vis[x] = 1;
for(int i=hd[x];i;i=nex[i]) {
int v=to[i];
if(!vis[v]) {
fa[v] = i;
fr[v] = x;
dfs(v);
}
}
}
vector<int>posi;
int main() {
// setIO("input");
scanf("%d%d%d%d",&n,&m,&os,&ot);
edges = 1;
for(int i=1;i<=m;++i) {
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
add(x, y, z);
add(y, x, z);
e[i]=Edge(x, y, z);
}
dfs(os);
if(!vis[ot]) {
printf("0\n");
printf("0\n");
}
else {
for(int i = ot; i!= os; i = fr[i]) {
posi.pb(fa[i] >> 1);
}
A = B = 0, fin = 2000000009;
for(int i = 0; i < posi.size(); ++ i) {
solve(posi[i]);
}
if(A + B == 0) {
printf("-1\n");
}
else {
printf("%d\n", fin);
// 留给后续了.
printf("%d\n", (A > 0) + (B > 0));
printf("%d", A);
if(B) printf(" %d", B);
}
}
return 0;
}
e-Government
来源:CF163E, 2700
没什么难度度的 AC 自动机 + DFS 序,没调就过了.
#include <cstdio>
#include <vector>
#include <cstring>
#include <queue>
#include <algorithm>
#define N 1000009
#define ll long long
#define pb push_back
#define ls now << 1
#define rs now << 1 | 1
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
struct BIT {
int C[N];
int lowbit(int x) {
return x & (-x);
}
void upd(int x, int v) {
while(x < N) {
C[x] += v;
x += lowbit(x);
}
}
int query(int x) {
int tmp = 0;
while(x > 0) {
tmp += C[x];
x -= lowbit(x);
}
return tmp;
}
}T;
char str[N];
queue<int>q;
vector<int>G[N];
int m,n,ch[N][26],fail[N],cnt,end[N],pos[N],scc,S[N],E[N];
void build() {
for(int i=0;i<26;++i) if(ch[0][i]) q.push(ch[0][i]);
while(!q.empty()) {
int u=q.front(); q.pop();
for(int i=0;i<26;++i) {
int v = ch[u][i];
if(!v) {
ch[u][i] = ch[fail[u]][i];
continue;
}
fail[v] = ch[fail[u]][i];
q.push(v);
}
}
}
void dfs(int x) {
S[x] = ++ scc;
for(int i=0;i<G[x].size();++i) {
dfs(G[x][i]);
}
E[x] = scc;
}
int IN[N];
int main() {
// setIO("input");
scanf("%d%d",&m,&n);
for(int i=1;i<=n;++i) {
scanf("%s",str+1);
int len=strlen(str+1),p=0;
for(int j=1;j<=len;++j) {
if(!ch[p][str[j]-'a']) {
ch[p][str[j]-'a'] = ++cnt;
}
p = ch[p][str[j]-'a'];
}
// ++end[p];
pos[i] = p;
}
build();
for(int i=1;i<=cnt;++i) {
G[fail[i]].pb(i);
}
dfs(0);
for(int i=1;i<=n;++i) {
// pos[i]
// T.update(1, scc, 1, S[pos[i]], E[pos[i]], 1);
T.upd(S[pos[i]], 1);
T.upd(E[pos[i]] + 1, -1);
IN[i] = 1;
}
for(int i=1;i<=m;++i) {
scanf("%s", str + 1);
int lim = strlen(str + 1);
if(str[1] == '+' || str[1] == '-') {
int num = 0;
for(int j=2;j<=lim;++j) {
num = num * 10 + (str[j] - '0');
}
if(str[1] == '+') {
if(!IN[num]) {
IN[num] = 1;
T.upd(S[pos[num]], 1);
T.upd(E[pos[num]] + 1, -1);
}
}
else {
if(IN[num]) {
IN[num] = 0;
T.upd(S[pos[num]], -1);
T.upd(E[pos[num]] + 1 , 1);
}
}
}
else {
int p = 0;
ll ans = 0;
for(int j = 2; j <= lim; ++ j) {
p = ch[p][str[j] - 'a'];
ans += T.query(S[p]);
}
printf("%lld\n", ans);
}
}
return 0;
}
Tree Modification
来源:CF1375G, 2800
这道题好像有这么二分图解法,太神了我不会.
不妨考虑用树形 $\mathrm{DP}$ 来求解这道题.
假设钦定 $\mathrm{x}$ 为根,那么所选取的点一定是深度依次递增的.
而且一定是先处理完子树再处理当前点.
令 $\mathrm{f,g,h,m}$ 分别表示 $\mathrm{x}$ 为子树的答案以及为根的答案和其他东西.
然后推一下换根 $\mathrm{DP}$ 的公式,发现 $\mathrm{g[v]=h[x]-1}$, 对于相邻点成立.
#include <cstdio>
#include <vector>
#include <cstring>
#include <algorithm>
#define N 200009
#define ll long long
#define pb push_back
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
vector<int>G[N];
int n, f[N], g[N], h[N], m[N];
void dfs1(int x, int ff) {
for(int i=0;i<G[x].size();++i) {
int v=G[x][i];
if(v==ff) continue;
dfs1(v, x);
m[x] += (f[v] + 1);
f[x] += m[v] ;
}
}
void dfs2(int x, int ff) {
for(int i=0;i<G[x].size();++i) {
int v=G[x][i];
if(v==ff) continue;
h[v] = g[x] + 1;
g[v] = h[x] - 1;
dfs2(v, x);
}
}
int main() {
// setIO("input");
scanf("%d",&n);
for(int i=1;i<n;++i) {
int x, y;
scanf("%d%d",&x,&y);
G[x].pb(y);
G[y].pb(x);
}
dfs1(1, 0);
g[1] = f[1], h[1] = m[1], dfs2(1, 0);
int ans = N ;
for(int i=1;i<=n;++i) {
ans = min(ans, g[i]);
}
printf("%d\n", ans);
return 0;
}
Rectangle Painting 1
来源:CF1198D, 2300
数据范围不大,考虑用 $\mathrm{DP}$ 来求解.
本题中的覆盖操作可以将整个矩阵分成若干个子矩阵,而且这些子矩阵还不会有重合.
不妨每次枚举最靠左的分割线和最靠下的分割线.
枚举完分割线后就可以递归成两个子问题了,用记忆化搜索来搜一下就行.
#include <cstdio>
#include <vector>
#include <cstring>
#include <algorithm>
#define N 52
#define ll long long
#define pb push_back
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
char str[N][N];
int n,f[N][N][N][N];
/*
(x1, x2, y1, y2)
(x1 < x2, y1 < y2)
*/
int dfs(int l1, int r1, int l2, int r2) {
if(l1 == r1 && l2 == r2) {
return f[l1][r1][l2][r2];
}
if(f[l1][r1][l2][r2] != -1) return f[l1][r1][l2][r2];
int det = max(r1 - l1 + 1, r2 - l2 + 1);
for(int i=l1;i<r1;++i) {
det = min(det, dfs(l1, i, l2, r2) + dfs(i + 1, r1, l2, r2));
}
for(int i=l2;i<r2;++i) {
det = min(det, dfs(l1, r1, l2, i) + dfs(l1, r1, i + 1, r2));
}
return f[l1][r1][l2][r2] = det;
}
int main() {
// setIO("input");
scanf("%d",&n);
memset(f,-1,sizeof(f));
for(int i=1;i<=n;++i) {
scanf("%s",str[i]+1);
for(int j=1;j<=n;++j) {
f[j][j][i][i]=(str[i][j]=='#');
}
}
printf("%d\n", dfs(1, n, 1, n));
return 0;
}
Antimatter
来源:CF383D, 2300
题目描述很邪乎,但是容易发现就是一个背包嘛.
稍微不同的是这个背包要求元素连续,所以在 $\mathrm{DP}$ 的过程中特判一下.
#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 10004
#define ll long long
#define setIO(s) freopen(s".in","r",stdin)
#define pb push_back
using namespace std;
const int mod = (int)1e9+7;
int f[1003][N + N],n,a[N];
int ID(int x) {
return x + N;
}
int main() {
// setIO("input");
scanf("%d",&n);
for(int i=1;i<=n;++i) {
scanf("%d",&a[i]);
}
int sum = 0 , ans = 0 ;
for(int i=1;i<=n;++i) {
for(int j = -sum + a[i]; j <= sum + a[i]; ++ j) {
( f[i][ID(j)] += f[i - 1][ID(j - a[i])] ) %= mod;
}
for(int j = -sum - a[i]; j <= sum - a[i]; ++ j) {
( f[i][ID(j)] += f[i - 1][ID(j + a[i])] ) %= mod;
}
(f[i][ID(a[i])] += 1) %= mod;
(f[i][ID(-a[i])] += 1) %= mod;
(ans += f[i][ID(0)]) %= mod;
sum += a[i];
}
printf("%d\n", ans);
return 0;
}
Cyclical Quest
来源:CF235C, 2600
把询问串倍长然后放到模式串的后缀自动机上跑一遍就行.
然后一定注意后缀自动机的 $\mathrm{len[x]}$ 数组所表示的是一个区间,所以要特判当前匹配的长度.
匹配失败调 $\mathrm{fail[x]}$ 的复杂度是有保障的,因为在自动机中的深度会减小,而增加的每次最多为 $1$.
所以最多只会减小 $\mathrm{n}$ 次,那么回退的次数就是线性的了.
#include <cstdio>
#include <vector>
#include <cstring>
#include <algorithm>
#define N 2000002
#define ll long long
#define pb push_back
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
char str[N];
vector<int>G[N];
int fa[20][N];
int n,Q,pre[N],len[N],size[N],mk[N],ch[N][27],last,tot;
void extend(int c) {
int np=++tot,p=last;
len[np]=len[p]+1,last=np;
for(;p&&!ch[p][c];p=pre[p]) ch[p][c]=np;
if(!p) pre[np]=1;
else {
int q=ch[p][c];
if(len[q]==len[p]+1) pre[np]=q;
else {
int nq=++tot;
len[nq]=len[p]+1;
memcpy(ch[nq],ch[q],sizeof(ch[q]));
pre[nq]=pre[q],pre[np]=pre[q]=nq;
for(;p&&ch[p][c]==q;p=pre[p]) ch[p][c]=nq;
}
}
++size[last];
}
void dfs(int x) {
fa[0][x]=pre[x];
for(int i=1;i<=19;++i) fa[i][x]=fa[i-1][fa[i-1][x]];
for(int i=0;i<G[x].size();++i) {
dfs(G[x][i]);
size[x]+=size[G[x][i]];
}
}
int jump(int x,int d) {
for(int i=19;i>=0;--i) {
if(len[fa[i][x]] >= d) x = fa[i][x];
}
return x;
}
int main() {
// setIO("input");
scanf("%s",str+1);
n=strlen(str+1);
last=tot=1;
for(int i=1;i<=n;++i) {
extend(str[i]-'a');
}
for(int i=2;i<=tot;++i) {
G[pre[i]].pb(i);
}
dfs(1);
scanf("%d",&Q);
for(int i=1;i<=Q;++i) {
scanf("%s",str+1);
int m=strlen(str+1);
int d=m;
for(int j=1;j<=d;++j) str[++m]=str[j];
int p=1,ac=0;
ll ans=0ll;
for(int j=1;j<=m;++j) {
while(p && !ch[p][str[j]-'a']) p=pre[p],ac=len[p];
p=ch[p][str[j]-'a'],++ac;
if(!p) p = 1,ac=0;
if(ac >= d) {
int cur = jump(p, d);
if(mk[cur] != i) {
mk[cur] = i;
ans += size[cur];
}
}
}
printf("%lld\n",ans);
}
return 0;
}
Carrots for Rabbits
来源:CF1428E, 2200
开始的想法是二分最大值,但是有剩余部分不好处理.
考虑用堆贪心.
显然,对于一个数字来说,分的越多越小.
不妨先让每个数字分成一份,然后将该数分成两份的差值作为关键字放到堆里.
每次取堆顶,然后更新堆.
这样做可以保证每次都减掉最多而且最优.
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
#define N 1000009
#define ll long long
#define mp make_pair
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
int a[N],n,K;
ll calc(int x, int y) {
if(x % y == 0) {
return 1ll * y * (x / y) * (x / y);
}
else {
int o = x / y;
int r = x % y;
return 1ll * r * (o + 1) * (o + 1) + 1ll * (y - r) * o * o;
}
}
priority_queue< pair<ll, pair<ll, ll> > > q;
int main() {
// setIO("input");
scanf("%d%d",&n,&K);
ll cost = 0;
for(int i=1;i<=n;++i) {
scanf("%d",&a[i]);
cost += 1ll * a[i] * a[i];
q.push(mp(calc(a[i], 1) - calc(a[i], 2), mp(a[i], 2)));
}
for(int i=1;i<=K-n;++i) {
cost -= q.top().first;
ll a = q.top().second.first;
ll b = q.top().second.second;
q.pop();
q.push(mp(calc(a, b) - calc(a, b + 1), mp(a, b + 1)));
}
printf("%lld\n", cost);
return 0;
}
Lizards and Basements 2
来源:CF6D, 2600
显然操作顺序是无所谓的,不妨考虑从左向右打.
令 $\mathrm{f[i][a][b]}$ 表示考虑到 $\mathrm{i}$, $\mathrm{i}$ 之前(不包括)$\mathrm{i}$ 全被打掉的代价.
因为 $\mathrm{i}$ 还受 $\mathrm{i+1}$ 的影响,所以定义状态的时候不能包括 $\mathrm{i}$.
一定要注意 $1$ 不能打,然后就是大力 $\mathrm{DP}$ 就好了.
#include <cstdio>
#include <cstring>
#include <algorithm>
#define ll long long
#define pb push_back
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
const int inf=10000000;
int dp[12][20][20],h[20],fin[20],n,A,B;
int main() {
// setIO("input");
scanf("%d%d%d",&n,&A,&B);
for(int i=1;i<=n;++i) {
scanf("%d",&h[i]);
}
memset(dp, 0x3f, sizeof(dp));
dp[1][0][0] = 0;
int ans = inf;
for(int i=2;i<=n;++i) {
for(int a=0;a<=16;++a) {
for(int b=0;b<=16;++b) {
for(int x=0;x<=16;++x)
for(int y=0;y<=16;++y) {
if(dp[i-1][x][y] > inf || y > a) continue;
if(i == n && b > 0) continue;
if(i == 2 && a) continue;
if(x * B + a * A + b * B > h[i - 1]) {
dp[i][a][b]=min(dp[i][a][b], dp[i-1][x][y] + a - y + b);
}
}
}
}
}
int sta = 0;
for(int i=0;i<=16;++i) {
if(B * i > h[n]) {
if(dp[n][i][0] < ans) {
ans = dp[n][i][0];
sta = i;
}
}
}
printf("%d\n", ans);
// 枚举第 i 个选了几次.
int cur = sta, prev = 0;
for(int i=n-1;i>1;--i) {
fin[i] = cur;
for(int j=0;j<=16;++j) {
if((dp[i][j][cur]+prev==dp[i+1][cur][prev])&&(j*B+cur*A+prev*B>h[i])) {
prev = cur;
cur = j;
break;
}
}
}
for(int i=2;i<n;++i) {
for(int j=1;j<=fin[i];++j) printf("%d ", i);
}
return 0;
}
Marcin and Training Camp
来源:CF1230D, 1700
显然,对于任意数字 $\mathrm{x}$ 都要求有 $\mathrm{y}$ 使得 $\mathrm{y}$ 包含 $\mathrm{x}$.
然后发现唯一的可行方式就是末端一定是两个相等的数字.
然后做法就出来了:先把相等的数字拿出来,然后看看出现一次的能否被他们包含即可.
#include <bits/stdc++.h>
#define ll long long
#define N 7009
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
int n ;
ll a[N], b[N];
map<ll,int>mp;
int main() {
// setIO("input");
scanf("%d",&n);
for(int i=1;i<=n;++i) scanf("%lld",&a[i]), mp[a[i]] ++ ;
for(int i=1;i<=n;++i) scanf("%lld",&b[i]);
ll sum = 0 ;
for(int i=1;i<=n;++i) {
if(mp[a[i]] > 1) {
sum += b[i];
}
}
if(!sum) {
printf("0\n");
}
else {
for(int i=1;i<=n;++i) {
if(mp[a[i]] == 1) {
for(int j=1;j<=n;++j) {
if(((a[i] & a[j]) == a[i]) && mp[a[j]] > 1) {
sum += b[i];
break;
}
}
}
}
printf("%lld\n", sum);
}
return 0;
}
Konrad and Company Evaluation
来源:CF1230F, 2400
考虑暴力:每次更新一个点时就一次遍历相邻点,暴力修改.
这么做每次修改的复杂度是一个点的度数,如果总是修改度数大的就不行了.
但是度数大的点比较少,如果设块大小为 $\mathrm{B}$ 的话不超过 $\mathrm{\frac{n}{B}}$ 个.
然后对于度数大的点发现每次只修改与其相邻且权值大于该点的点的话复杂度是正确的.
这部分复杂度就是 $O(\mathrm{n^2}{B})$ 的.
然后要求每次修改一个点时要把度数大的点拿出来,然后给这些点暴力存上,复杂度是 $O(\mathrm{\frac{n^2}{B} \log n})$ 的.
度数小的点就是 $O(B)$ 的复杂度.
由于瓶颈在度数大的点的修改上,所以这个 $\mathrm{B}$ 要尽量大一些.
#include <cstdio>
#include <cmath>
#include <vector>
#include <cstring>
#include <algorithm>
#define N 130002
#define ll long long
#define pb push_back
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
vector<int>G[N],F[N];
ll ans;
// int is[402][N];
int B,n,m,fu[N];
int in[N],ou[N],bu[N],id[N],val[N],cnt;
int main() {
// setIO("input");
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i) {
val[i]=i;
}
for(int i=1;i<=m;++i) {
int x, y;
scanf("%d%d",&x,&y);
G[x].pb(y);
G[y].pb(x);
if(x > y)
swap(x, y);
++in[x];
++ou[y];
}
B = 400;
ll ans = 0ll;
for(int i=1;i<=n;++i) {
if(G[i].size() > B) {
id[i] = ++ cnt;
bu[cnt] = i;
}
ans += 1ll * in[i] * ou[i];
}
for(int i=1;i<=n;++i) sort(G[i].begin(), G[i].end());
for(int i=1;i<=n;++i) {
if(G[i].size() > B) {
for(int j=0;j<G[i].size();++j) {
if(val[G[i][j]] > val[i])
F[i].pb(G[i][j]);
}
}
}
// bu[1]......bu[cnt]: 度数非常大的块块.
printf("%lld\n", ans);
int Q;
scanf("%d", &Q);
for(int i=1;i<=Q;++i) {
int x;
scanf("%d",&x);
// 处理大哥们.
for(int j=1;j<=cnt;++j) {
int p = bu[j];
if(G[p].size()) {
if(G[p][G[p].size()-1] >= x) {
int o = lower_bound(G[p].begin(), G[p].end(), x) - G[p].begin();
if(G[p][o] == x) {
F[p].pb(x);
}
}
}
}
// 处理小的东西.
if(G[x].size() <= B) {
ans -= 1ll * in[x] * ou[x];
for(int j=0;j<G[x].size();++j) {
int v=G[x][j];
if(val[v] > val[x]) {
ans -= 1ll * in[v] * ou[v];
ou[v] -- ;
in[v] ++ ;
ans += 1ll * in[v] * ou[v];
in[x] -- ;
ou[x] ++ ;
}
}
ans += 1ll * in[x] * ou[x];
val[x] = i + n;
}
else {
ans -= 1ll * in[x] * ou[x];
// printf("%lld\n", ans);
for(int j=0;j<F[x].size();++j) {
int v=F[x][j];
if(fu[v] == i) continue;
fu[v] = i;
// 后来与 x 相邻且变为最大的边.
if(val[v] > val[x]) {
ans -= 1ll * in[v] * ou[v];
ou[v] -- ;
in[v] ++ ;
ans += 1ll * in[v] * ou[v];
in[x] -- ;
ou[x] ++ ;
}
}
// printf("%d %d %d\n",x, in[x], ou[x]);
ans += 1ll * in[x] * ou[x];
val[x] = i + n;
F[x].clear();
}
printf("%lld\n", ans);
}
return 0;
}
Sum Queries
来源:CF1217E, 2300
考虑不合法的条件:合的第 $\mathrm{i}$ 位在所有元素中都找不到.
然后仔细想想如果存在不合法序列,则两个数字一定就能构造出来.
推出来这个性质后直接拆位,对于每一位维护区间最小和次小值即可.
实现的话开了 10 棵线段树,动态开点的话空间能小点,不过感觉没啥必要.
#include <cstdio>
#include <cstring>
#include <vector>
#include <algorithm>
#define N 200009
#define ll long long
#define pb push_back
#define ls now << 1
#define rs now << 1 | 1
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
const int inf=2000000009;
int n,m,a[N],A[N],val[N];
struct SGT {
struct data {
int mi,se;
data(){ mi = se = inf; }
data operator+(const data b) const {
data c;
if(mi == b.mi) {
c.mi = c.se = mi;
}
else if(mi < b.mi) {
c.mi = mi;
c.se = min(se, b.mi);
}
else {
c.mi = b.mi;
c.se = min(b.se, mi);
}
return c;
}
}s[N<<2];
void build(int l,int r,int now) {
if(l==r) {
s[now].se=s[now].mi=inf;
if(A[l]) s[now].mi=val[l];
return ;
}
int mid=(l+r)>>1;
build(l,mid,ls);
build(mid+1,r,rs);
s[now]=s[ls]+s[rs];
}
data query(int l,int r,int now,int L,int R) {
if(l>=L&&r<=R) return s[now];
int mid=(l+r)>>1;
if(L<=mid && R>mid) return query(l,mid,ls,L,R)+query(mid+1,r,rs,L,R);
else if(L<=mid) return query(l,mid,ls,L,R);
else return query(mid+1,r,rs,L,R);
}
void update(int l,int r,int now,int p,int v) {
if(l==r) {
s[now].mi = v;
return ;
}
int mid=(l+r)>>1;
if(p<=mid) update(l,mid,ls,p,v);
else update(mid+1,r,rs,p,v);
s[now]=s[ls]+s[rs];
}
ll Q(int l, int r) {
data o = query(1, n, 1, l, r);
if(o.se < inf) return o.mi + o.se;
else return -1;
}
}T[10];
int bin[10];
int main() {
// setIO("input");
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i) {
scanf("%d",&a[i]);
val[i] = a[i];
}
bin[0]=1;
for(int i=1;i<10;++i) bin[i]=bin[i-1]*10;
for(int i=0;i<10;++i) {
// 后面有 i 个 0.
for(int j=1;j<=n;++j) {
A[j] = (a[j] / bin[i]) % 10;
}
T[i].build(1, n, 1);
}
for(int i=1;i<=m;++i) {
int op,x,y;
scanf("%d%d%d",&op,&x,&y);
if(op==1) {
// a[x] = y;
for(int j=0;j<10;++j) {
int p = (a[x] / bin[j]) % 10;
if(p) T[j].update(1, n, 1, x, inf);
}
a[x] = y;
val[x] = y;
for(int j=0;j<10;++j) {
int p = (a[x] / bin[j]) % 10;
if(p) T[j].update(1, n, 1, x, val[x]);
}
}
else {
ll p = 1000000000000ll;
for(int j=0;j<10;++j) {
ll de = T[j].Q(x, y);
if(de != -1) p = min(p, de);
}
printf("%lld\n", p == 1000000000000ll ? -1 : p);
}
}
return 0;
}
Coloring Edges
来源:CF1217D, 2100
首先,没有环的话用 1 就好了.
如果出现环,可以把 DFS 树提出来,则必有返祖边和树边.
不妨让树边为 $0$, 返祖边为 $1$, 因为根据 DFS 树那一套每个环都得有一个返祖边.
但其实我们可以规定让编号小指向编号大的定为 2,其余定为 1.
因为环的本质是返祖,而这样一定可以把返祖关系都表示出来.
#include <cstdio>
#include <vector>
#include <stack>
#include <cstring>
#include <algorithm>
#define N 100009
#define ll long long
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
int n,m;
stack<int>S;
int co, vis[N];
int A[N], B[N];
int hd[N],to[N],nex[N],val[N],col[N],dfn[N],low[N],tim,edges;
void add(int u, int v) {
nex[++edges]=hd[u];
hd[u]=edges;
to[edges]=v;
}
void tarjan(int x) {
S.push(x);
vis[x] = 1;
low[x]=dfn[x]=++tim;
for(int i=hd[x];i;i=nex[i]) {
int v=to[i];
if(vis[v] == 0) {
val[i] = 0;
tarjan(v);
low[x] = min(low[x], low[v]);
}
else if(vis[v] != -1) {
val[i] = 1;
low[x] = min(low[x], dfn[v]);
}
}
if(dfn[x] == low[x]) {
++ co;
for(;;) {
int p = S.top();
S.pop();
vis[p] = -1;
if(x == p) break;
}
}
}
int main() {
// setIO("input");
scanf("%d%d",&n,&m);
for(int i=1;i<=m;++i) {
int x,y;
scanf("%d%d",&x,&y);
add(x, y);
A[i] = x;
B[i] = y;
}
for(int i=1;i<=n;++i) {
if(vis[i] == 0) tarjan(i);
}
if(co == n) {
printf("1\n");
for(int i=1;i<=m;++i) {
printf("1 ");
}
}
else {
printf("2\n");
for(int i=1;i<=edges;++i) {
printf("%d ", 1 + (A[i] < B[i]));
// printf("%d ", val[i] + 1);
}
}
return 0;
}
New Year and Old Subsequence
来源:CF750E, 2600
只需考虑 $2017$ 的状态.
令 $0$ 表示没开始,$1$ 表示有 $2$, $2$ 表示有 $20$.....
令 $\mathrm{f[i][sta]}$ 表示 $\mathrm{DP}$ 到 $\mathrm{i}$, 状态为 $\mathrm{sta}$ 的最小删除步数.
然后在进行状态转移的时候发现每次只和 $\mathrm{sta}$ 有关,而这个 $\mathrm{sta}$ 种类却很少.
转移是固定的,而起点也是容易得知的,故可以利用线段树维护矩阵乘法来做.
#include <cstdio>
#include <vector>
#include <cstring>
#include <algorithm>
#define N 200009
#define ll long long
#define pb push_back
#define ls now << 1
#define rs now << 1 | 1
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
const int inf = 100000000;
char str[N];
int n,Q,A[N];
struct M {
int c[6][6];
M() { memset(c,0x3f,sizeof(c));}
M operator*(const M b) const {
M fi;
for(int i=0;i<5;++i) {
for(int j=0;j<5;++j) {
for(int k=0;k<5;++k)
fi.c[i][j]=min(fi.c[i][j], c[i][k]+b.c[k][j]);
}
}
return fi;
}
}s[N<<2],G[N];
void build(int l,int r,int now) {
if(l == r) {
s[now] = G[l];
return;
}
int mid=(l+r)>>1;
build(l,mid,ls);
build(mid+1,r,rs);
s[now]=s[ls]*s[rs];
}
M query(int l,int r,int now,int L,int R) {
if(l>=L&&r<=R) return s[now];
int mid=(l+r)>>1;
if(L<=mid&&R>mid) return query(l,mid,ls,L,R)*query(mid+1,r,rs,L,R);
else if(L<=mid) return query(l,mid,ls,L,R);
else return query(mid+1,r,rs,L,R);
}
int main() {
// setIO("input");
scanf("%d%d",&n,&Q);
scanf("%s",str+1);
for(int i=1;i<=n;++i) {
A[i]=str[i]-'0';
// 构建当前的矩阵.
memset(G[i].c, 0x3f, sizeof(G[i].c));
G[i].c[0][0]=(A[i]==2);
G[i].c[1][1]=(A[i]==0);
G[i].c[2][2]=(A[i]==1);
G[i].c[3][3]=(A[i]==6||A[i]==7);
G[i].c[4][4]=(A[i]==6);
if(A[i]==2) G[i].c[0][1]=0;
if(A[i]==0) G[i].c[1][2]=0;
if(A[i]==1) G[i].c[2][3]=0;
if(A[i]==7) G[i].c[3][4]=0;
}
build(1, n, 1);
for(int i=1;i<=Q;++i) {
int l,r;
scanf("%d%d",&l,&r);
M fin = query(1, n, 1, l, r);
int cur = fin.c[0][4];
if(cur <= n) {
printf("%d\n", cur);
}
else {
printf("-1\n");
}
}
return 0;
}
Palindrome Degree
来源:CF7D, 2200
只需要判断前 $\mathrm{i}$ 个字符是否为回文串就行.
而判断回文串的条件就是看正反哈希值是否相等.
由于空间吃紧,所以不能采取数组的方式来求哈希,不过用 3 个变量既可以代替.
#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 5000009
#define ll long long
#define pb push_back
#define ull unsigned long long
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
const ll B = 32767ll;
const ll mod = 998244353;
char str[N];
int sum[N],n;
ll f, b, base;
void get_hash(int x) {
f = (f * B % mod + (ll)str[x] + mod) % mod;
b = (b + (ll)str[x] * base % mod + mod) % mod;
base = base * B % mod;
}
int main() {
// setIO("input");
scanf("%s", str + 1);
n = strlen(str + 1);
base = 1;
for(int i=1;i<=n;++i) {
get_hash(i);
if(f == b) {
sum[i] = sum[i / 2] + 1;
}
}
ll ans=0;
for(int i=1;i<=n;++i) {
ans += 1ll * sum[i];
}
printf("%lld\n", ans);
return 0;
}
Strange Housing
来源:CF1470D, 2200
不妨考虑构建完毕 $\mathrm{n-1}$ 个点的情况,如何构造 $\mathrm{n}$ 个点.
加入新点时,若原图所有相邻点都未被染色则让新点染色,否则就不让新点染色.
这么做一遍之后如果所有点都被访问过就合法,否则就不合法.
在加入新点时要加入和当前连通块联通的部分,于是用队列来维护即可.
#include <cstdio>
#include <vector>
#include <cstring>
#include <queue>
#include <algorithm>
#define N 300009
#define ll long long
#define pb push_back
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
int n,m,col[N], vis[N];
vector<int>G[N];
queue<int>q;
void solve() {
scanf("%d%d",&n,&m);
for(int i=1;i<=m;++i) {
int x, y;
scanf("%d%d",&x,&y);
G[x].pb(y);
G[y].pb(x);
}
q.push(1);
while(!q.empty()) {
int u = q.front(); q.pop();
int flag = 0;
for(int i = 0;i < G[u].size() ; ++ i) {
int v = G[u][i];
flag |= col[v];
if(!vis[v]) q.push(v), vis[v] = 1;
}
if(flag == 0) {
col[u] = 1;
}
else {
col[u] = 0;
}
}
int cn = 0;
for(int i = 1; i <= n ; ++ i) if(!vis[i]) cn = 1;
if(cn) {
printf("NO\n");
}
else {
printf("YES\n");
int num = 0;
for(int i = 1; i <= n ; ++ i) num += col[i];
printf("%d\n", num);
for(int i = 1; i <= n ; ++ i) if(col[i]) printf("%d ", i);
printf("\n");
}
for(int i = 0 ; i <= n ; ++ i) G[i].clear(), col[i] = vis[i] = 0;
}
int main() {
// setIO("input");
int T;
scanf("%d",&T);
while(T--) solve();
return 0;
}
Arpa’s letter-marked tree and Mehrdad’s Dokhtar-kosh paths
来源:CF741D, 2900
显然,对于一个路径是否合法只需要考虑每种字符出现次数奇偶即可.
然后字符种类不到 22 种,可以状态压缩.
对于路径 $\mathrm{(x,y)}$ 发现该路径的状态为 $\mathrm{dis[x] \bigoplus dis[y]}$.
其中 $\mathrm{dis[x]}$ 表示 $\mathrm{x}$ 到 $1$ 的路径异或和.
然后这个东西利用 $\mathrm{DSU}$ 来做就好了.
#include <cstdio>
#include <vector>
#include <cstring>
#include <algorithm>
#define N 500009
#define pb push_back
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
int bin[30], dep[N], ma, dis[N], bu[1 << 23], ans[N];
int fa[N],val[N],size[N],son[N],hd[N],to[N],nex[N],edges,n;
void add(int u,int v,int c) {
nex[++edges]=hd[u];
hd[u]=edges;
to[edges]=v;
val[edges]=c;
}
void init() {
for(int i=0;i<23;++i) bin[i] = 1 << i;
}
// dfs1: 得到重儿子.
void dfs1(int x) {
size[x] = 1;
for(int i=hd[x];i;i=nex[i]) {
int v=to[i];
dep[v] = dep[x] + 1;
dis[v] = dis[x] ^ bin[val[i]];
dfs1(v);
size[x] += size[v];
if(size[v] > size[son[x]]) {
son[x] = v;
}
}
}
void calc(int x, int tp) {
// (dis[x], dep[x])
if(bu[dis[x]])
ma = max(ma, bu[dis[x]] + dep[x] - dep[tp] * 2);
for(int i=0;i<22;++i) {
if(bu[bin[i] ^ dis[x]]) {
ma = max(ma, bu[bin[i] ^ dis[x]] + dep[x] - dep[tp] * 2);
}
}
for(int i = hd[x]; i ; i = nex[i]) {
calc(to[i], tp);
}
}
void ADD(int x) {
bu[dis[x]] = max(bu[dis[x]], dep[x]);
for(int i = hd[x]; i ; i = nex[i]) {
ADD(to[i]);
}
}
void DEL(int x) {
bu[dis[x]] = 0;
for(int i=hd[x];i;i=nex[i]) {
DEL(to[i]);
}
}
void dfs2(int x, int ty) {
for(int i = hd[x]; i ; i = nex[i]) {
int v = to[i];
if(v == son[x]) continue;
dfs2(v, 0);
// 不保留轻儿子.
}
int cur = 0;
if(son[x]) {
dfs2(son[x], 1);
cur = bu[dis[x]] - dep[x];
for(int i=0;i<22;++i) cur = max(cur, bu[bin[i] ^ dis[x]] - dep[x]);
}
// son[x] 处理完毕了.
ma = cur;
for(int i = hd[x]; i ; i = nex[i]) {
int v = to[i];
if(v == son[x]) continue;
calc(v, x);
ADD(v);
ma = max(ma, bu[dis[x]] - dep[x]);
for(int j=0;j<22;++j) ma = max(ma, bu[bin[j] ^ dis[x]] - dep[x]);
}
bu[dis[x]] = max(bu[dis[x]], dep[x]);
ans[x] = ma;
// printf("%d %d\n", x, ans[x]);
if(ty == 0) {
// 该全部删掉了.
DEL(x);
}
}
void dfs3(int x) {
for(int i = hd[x]; i ; i = nex[i]) {
dfs3(to[i]);
ans[x] = max(ans[x], ans[to[i]]);
}
}
int main() {
// setIO("input");
scanf("%d",&n);
init();
for(int i=2;i<=n;++i) {
scanf("%d",&fa[i]);
char ch[2];
scanf("%s", ch);
add(fa[i], i, ch[0] - 'a');
}
dfs1(1);
dfs2(1, 1);
dfs3(1);
for(int i=1;i<=n;++i) printf("%d ", ans[i]);
printf("\n");
return 0;
}
Ehab's REAL Number Theory Problem
来源:CF1325E, 2600
分析一下发现每个数的质因子种类最多为 2.
显然,平方因子对答案没有贡献,都应该被除掉.
如果消除后的序列有 1,则直接输出 1.
否则,对于所有数字只有 2 种形式:$\mathrm{1 \times p}$ 与 $\mathrm{p \times q}$.
可以将每个数的 2 个因子连边,问题转化成求无向图的最小环.
由于环的点肯定包含一个小于 1000 的质数,且环的边长为1,可以枚举起点,然后暴力 $\mathrm{bfs}$.
$\mathrm{bfs}$ 的过程发现返祖边就更新答案,否则加入到队列中继续更新.
#include <cstdio>
#include <vector>
#include <queue>
#include <cstring>
#include <algorithm>
#define N 1000009
#define ll long long
#define pb push_back
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
queue<int>q;
int hd[N], to[N<<1], nex[N<<1], edges, ans;
int vis[N], prime[N], a[N], n, cnt, d[N], fm[N];
void add(int u, int v) {
nex[++edges] = hd[u];
hd[u] = edges;
to[edges] = v;
}
void init() {
for(int i=2;i<N;++i) {
if(!vis[i]) {
prime[++cnt] = i;
for(int j = i + i; j < N; j += i) {
vis[j] = 1;
}
}
}
}
int divide(int x) {
for(int i=1;prime[i] * prime[i] <= x; ++ i) {
if(x % prime[i] == 0) {
int cn = 0;
while(x % prime[i] == 0) x /= prime[i], ++ cn;
if(cn % 2 == 1) x *= prime[i];
}
}
return x;
}
void bfs(int s) {
memset(d, 0, sizeof(d));
memset(fm,0, sizeof(fm));
q.push(s);
fm[s] = -1;
while(!q.empty()) {
int u = q.front(); q.pop();
for(int i = hd[u]; i ; i = nex[i]) {
int v = to[i];
if((i ^ 1) == fm[u]) continue;
if(fm[v]) {
ans = min(ans, d[u] + d[v] + 1);
}
else {
d[v] = d[u] + 1;
fm[v] = i;
q.push(v);
}
}
}
}
int main() {
// setIO("input");
init();
scanf("%d",&n);
for(int i=1;i<=n;++i) {
scanf("%d",&a[i]);
int cur = divide(a[i]);
a[i] = cur;
if(cur == 1) {
printf("1\n");
return 0;
}
}
edges = 1;
for(int i=1;i<=n;++i) {
// a[i]: 拆分之后的.
if(!vis[a[i]]) {
// 1 and a[i].
add(1, a[i]);
add(a[i], 1);
}
else {
int j=1;
for(j=1;prime[j]*prime[j]<=a[i];++j) {
if(a[i] % prime[j] == 0) break;
}
// prime[j] 为更小的那个质数.
int p = prime[j], q = a[i] / prime[j];
add(p, q);
add(q, p);
}
}
ans = N;
if(hd[1]) bfs(1);
for(int i=1;i<=cnt;++i) {
if(prime[i] > 1000) break;
if(hd[prime[i]]) bfs(prime[i]);
}
printf("%d\n", ans == N ? -1 : ans);
return 0;
}
A Simple Task
来源:CF11D, 2200
状压 DP.
令 $\mathrm{f[i][x][y]}$ 表示加入的点为 $\mathrm{i}$, 起点为 $\mathrm{x}$, 终点为 $\mathrm{y}$ 的方案.
但是我们可以强制钦定让环中编号最小的点为起点,可以省掉一个 $\mathrm{i}$.
然后转移的时候只可以向末断加点,不可以改变起点,否则会算重.
最后我们对于一个环其实从两个方向都算了一遍,所以要除以 2.
#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 20
#define ll long long
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
int n,m,num[1<<20],is[N][N],LG[1<<N];
ll f[1<<20][20];
int lowbit(int x) {
return x & (-x);
}
int main() {
// setIO("input");
scanf("%d%d",&n,&m);
for(int i=1;i<=m;++i) {
int x,y;
scanf("%d%d",&x,&y);
is[x][y] = is[y][x] =1 ;
}
for(int i=1;i<(1<<n);++i) {
num[i] = num[i - lowbit(i)] + 1;
}
for(int i=0;i<n;++i) LG[1 << i] = i;
ll ans = 0ll;
for(int i=1;i<(1<<n);++i) {
for(int j=0;j<n;++j) {
if((i&(1<<j)) == 0) continue;
if(num[i] == 1) {
f[i][j + 1] = 1;
}
if(num[i] > 2 && is[LG[lowbit(i)] + 1][j + 1]) {
ans += f[i][j + 1];
}
if(!f[i][j + 1]) continue;
for(int nex=0;nex<n;++nex) {
if((1 << nex) < lowbit(i)) continue;
if(i & (1 << nex)) continue;
// 新的,且不能是最小值.
if(is[j + 1][nex + 1])
f[i + (1 << nex)][nex + 1] += f[i][j + 1];
}
}
}
printf("%lld\n", ans / 2);
return 0;
}
Product Sum
来源:CF631E, 2600
先求出不进行操作的答案.
每次考虑 $\mathrm{(i,j)}$ 互换的贡献.
然后退一下式子发现这个贡献是 $\mathrm{a[i]}$ 为 $\mathrm{x}$ 的 $\mathrm{j}$ 提供系数的直线.
这个直线直接用李超线段树维护即可.
由于 $\mathrm{a[i]}$ 的值有负数, 所以在构建线段树时的左端点要注意一下.
#include <cstdio>
#include <vector>
#include <set>
#include <cstring>
#include <algorithm>
#define N 200009
#define M 2000002
#define ll long long
#define pb push_back
#define ls now<<1
#define rs now<<1|1
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
int a[N], tree[M<<2], n;
ll sum[N], suf[N];
struct Line {
ll k, b;
Line(ll k=0,ll b=0):k(k),b(b){}
}line[N];
ll calc(Line o, ll pos) {
return o.k * pos + o.b;
}
void update(int l,int r,int now,int L,int R,int id) {
if(l>=L&&r<=R) {
int mid = (l + r) >> 1;
if(calc(line[tree[now]], mid) < calc(line[id], mid)) swap(id, tree[now]);
// 向下传递的是 id.
if(l == r) return ;
if(calc(line[id], l) > calc(line[tree[now]], l))
update(l, mid, ls, L, R, id);
if(calc(line[id], r) > calc(line[tree[now]], r))
update(mid + 1, r, rs, L, R, id);
return ;
}
int mid=(l+r)>>1;
if(L<=mid) update(l,mid,ls,L,R,id);
if(R>mid) update(mid+1,r,rs,L,R,id);
}
void build(int l,int r,int now) {
tree[now] = 1;
if(l == r) return ;
int mid = (l + r) >> 1;
build(l, mid, ls);
build(mid + 1, r, rs);
}
ll query(int l, int r, int now, int p) {
if(l == r) {
return calc(line[tree[now]], p);
}
int mid = (l + r) >> 1;
ll cur = calc(line[tree[now]], p);
if(p <= mid) return max(cur, query(l, mid, ls, p));
else return max(cur, query(mid + 1, r, rs, p));
}
void build2(int l, int r, int now) {
tree[now] = n ;
if(l == r) return ;
int mid=(l+r)>>1;
build2(l, mid, ls);
build2(mid + 1, r, rs);
}
int main() {
// setIO("input");
scanf("%d",&n);
int len = 1000000;
// printf("%d\n", n);
for(int i=1;i<=n;++i) {
scanf("%d",&a[i]);
}
sum[0] = 0;
for(int i=1;i<=n;++i) {
sum[i] = sum[i - 1] + a[i];
line[i].k = i;
line[i].b = -sum[i - 1];
}
build(-len, len, 1);
ll fin = 0ll, ans;
for(int i = 1; i <= n ; ++ i) {
fin += 1ll * i * a[i];
}
ans = fin;
for(int i = 2; i <= n ; ++ i) {
ll det = sum[i - 1] - 1ll * i * a[i] + query(-len, len, 1, a[i]);
ans = max(ans, det + fin);
update(-len, len, 1, -len, len, i);
}
for(int i = n; i >= 1; -- i) {
suf[i] = suf[i + 1] + a[i];
line[i].k = i;
line[i].b = suf[i + 1];
}
build2(-len, len, 1);
for(int i = n - 1; i >= 1; -- i) {
ll det = -suf[i + 1] - 1ll * i * a[i] + query(-len, len, 1, a[i]);
ans = max(ans, fin + det);
update(-len, len, 1, -len, len, i);
}
printf("%lld\n", ans);
return 0;
}
Integer Game
来源:CF1375F, 2600
神仙交互,不看题解根本想不出来.
不妨考虑先手必胜要有怎样局面:
当前 $3$ 个数字构成等差数列且后手在上一轮对当前最大值位置操作了.
不妨在最开始的时候让后手对任意位置加上 $\mathrm{inf}$, 设为 $\mathrm{c}$.
然后第二步的时候只能对于更小的 $\mathrm{a,b}$ 操作了.
这个时候令 $\mathrm{k=2c-a-b}$.
加上之后第二次操作的数字就成了最大值,且相邻两项差为 $\mathrm{c-b}$.
这样,在 $3$ 步之内先手就取得了胜利.
#include <cstdio>
#include <cstring>
#include <algorithm>
#define ll long long
using namespace std;
ll a[4];
int x[4];
void print(ll x) {
printf("%lld\n", x);
printf("\n");
fflush(stdout);
}
int rd() {
ll o;
scanf("%lld", &o);
return (int)o;
}
int main() {
for(int i=1;i<=3;++i) {
scanf("%lld",&a[i]);
}
puts("First");
ll M = 100000000000ll;
print(M);
x[1] = rd();
// scanf("%lld",&x[1]);
a[x[1]] += M;
// 下一步:2c - a - b
ll d2 = a[x[1]] * 3 - a[1] - a[2] - a[3];
print(d2);
x[2] = rd();
// scanf("%d",&x[2]);
a[x[2]] += d2;
x[3] = 6 - x[1] - x[2];
// x[1]: 最大的.
// x[2]: 第二大的.
// x[3]: 当前最小.
//print(a[x[2]] - a[x[3]]);
printf("%lld\n", a[x[1]] - a[x[3]]);
return 0;
}
Arpa’s overnight party and Mehrdad’s silent entering
来源:CF741C, 2600
直接做没什么思路,看了下题解发现这是个图论题.
显然,对于情侣的配色就是个二分图染色,然后对于其他人也同理.
不妨将 $\mathrm{(2i, 2i-1)}$ 看成一对,然后一对之间连边.
要判断这种连边方式是否有解.
不难发现对于每一个点,最多连 2 条边,所以原图会构成一些链和环.
对于每一种环的情况,也一定是偶数个点,那么就不存在奇环了.
直接连边然后二分图染色就行了.
#include <cstdio>
#include <cstring>
#include <vector>
#include <algorithm>
#define N 2000009
#define ll long long
#define pb push_back
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
int n , ans[N], vis[N], A[N], B[N];
vector<int>G[N];
void dfs(int x) {
vis[x] = 1;
for(int i = 0; i < G[x].size() ; ++ i) {
int v = G[x][i];
if(!vis[v]) {
ans[v] = ans[x] ^ 1;
dfs(v);
}
}
}
int main() {
// setIO("input");
scanf("%d",&n);
for(int i=1;i<=n;++i) {
G[2 * i - 1].pb(2 * i);
G[2 * i].pb(2 * i - 1);
}
for(int i=1;i<=n;++i) {
int x, y;
scanf("%d%d",&x, &y);
G[x].pb(y);
G[y].pb(x);
A[i] = x;
B[i] = y;
}
for(int i = 1; i <= 2 * n ; ++ i) {
if(!vis[i]) dfs(i);
}
for(int i = 1; i <= n ; ++ i) {
printf("%d %d\n", ans[A[i]] + 1, ans[B[i]] + 1);
}
return 0;
}
Sonya and Problem Wihtout a Legend
来源:CF713C, 2300
直接弄这个严格递增序列不好搞,不妨考虑严格递增需要满足的条件.
发现,条件为 $\mathrm{i-j\leqslant a[i]-a[j]}$ 对任意 $\mathrm{i,j}$ 都成立.
然后化简一下就是 $\mathrm{a[j]-j \leqslant a[i]-i}$.
令 $\mathrm{c[i]}$ 表示化简后的结果,那只需令 $\mathrm{c[i]}$ 不减就行.
由不减的性质,序列中数字种类肯定不会变.
所以可以设 $\mathrm{f[i][j]}$ 表示前 $\mathrm{i}$ 个数,最后为 $\mathrm{j}$ 的修改次数.
这个东西用 $\mathrm{DP}$ 求一下就好了.
#include <cstdio>
#include <vector>
#include <cstring>
#include <algorithm>
#define N 3009
#define ll long long
#define pb push_back
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
int n , cnt ;
int a[N], c[N], A[N];
ll f[N][N];
int main() {
// setIO("input");
scanf("%d",&n);
for(int i=1;i<=n;++i) {
scanf("%d",&a[i]);
c[i] = a[i] - i;
}
cnt = n ;
c[++ cnt] = -N;
for(int i=1;i<=cnt;++i) A[i]=c[i];
sort(A+1,A+1+cnt);
for(int i=1;i<=cnt;++i) c[i]=lower_bound(A+1,A+1+cnt,c[i])-A;
memset(f, 0x3f, sizeof(f));
f[0][1] = 0;
ll fin = 1000000000000000ll;
for(int i=1;i<=n;++i) {
ll mi = f[i - 1][1];
for(int j=1;j<=cnt;++j) {
mi = min(mi, f[i - 1][j]);
f[i][j] = abs(A[c[i]] - A[j]) + mi;
if(i == n) fin = min(fin, f[i][j]);
}
}
printf("%lld\n", fin);
return 0;
}
Make k Equal
来源:CF1328F, 2200
思考最后的形式:
设答案为 $\mathrm{fin}$, 需要大于 $\mathrm{fin}$ 的都变为 $\mathrm{fin+1}$, 然后从中挑选不够的补齐.
对于小于 $\mathrm{fin}$ 的情况同理.
显然,答案只可能为序列中原数,或者相差值为 $1$ 的数字.
直接枚举答案然后利用前缀和算一算就好了.
#include <cstdio>
#include <vector>
#include <cstring>
#include <algorithm>
#include <map>
#define N 600009
#define ll long long
#define pb push_back
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
int n,K,cnt;
ll suf[N],pre[N];
int a[N],A[N],B[N];
map<int,int>co;
int get_pre(int pos) {
// 求 <= pos 的个数.
int p = lower_bound(B + 1, B + 1 + 1 + n, pos + 1) - B;
return p - 1;
}
int get_aft(int pos) {
// 求 >= pos 的个数.
return n - get_pre(pos - 1);
}
int main() {
// setIO("input");
scanf("%d%d",&n,&K);
for(int i=1;i<=n;++i) {
scanf("%d",&a[i]);
B[i] = a[i];
co[a[i]] ++ ;
if(co[a[i]] >= K) {
printf("0\n");
return 0;
}
A[++cnt] = a[i];
A[++cnt] = a[i] - 1;
A[++cnt] = a[i] + 1;
}
B[n + 1] = 1200000000;
sort(B+1, B+1+n);
for(int i = n; i >= 1; -- i)
suf[i] = suf[i + 1] + 1ll * B[i];
for(int i = 1; i <= n ; ++ i)
pre[i] = pre[i - 1] + 1ll * B[i];
sort(A+1, A+1+cnt);
ll ans = 10000000000000000ll;
for(int i=1;i<=cnt;++i) {
// 计算 A[i] 的答案.
int num = co[A[i]];
int det = K - num;
int prfix = get_pre(A[i] - 1);
int sufix = get_aft(A[i] + 1);
if(prfix >= det || sufix >= det) {
if(prfix >= det) {
ll dprfix = 1ll * prfix * (A[i] - 1) - pre[prfix];
ans = min(ans, dprfix + det);
}
if(sufix >= det) {
ll dsufix = suf[n - sufix + 1] - 1ll * sufix * (A[i] + 1);
ans = min(ans, dsufix + det);
}
}
else {
// 需要都到达目的地.
ll dsufix = suf[n - sufix + 1] - 1ll * sufix * (A[i] + 1) ;
ll dprfix = 1ll * prfix * (A[i] - 1) - pre[prfix];
ans = min(ans, dsufix + dprfix + det);
}
}
printf("%lld\n", ans);
return 0;
}

浙公网安备 33010602011771号