省选测试1
T1
给定长度为(n)的排列,(m)个询问.每次询问区间([l,r])中,最长的值域连续的一段.
最长的值域连续的一段是指从排列的([l,r])的区间中选出一些数, 使得这些数排序后构成了连续的一段正整数, 那么这些正整数就是一个值域连续段.
(n, m <= 5e4)
回滚莫队 + 并查集.
很显然莫队可做, 但是考试的时候并没有学过回滚莫队, 于是就用了普通莫队 + 线段树搞了一个(O(nsqrt n logn))的做法.
我们发现这道题的难点就在于如何删除一个数字的影响. 那我们可以考虑只有加入操作没有删除操作. 这正好就是回滚莫队了.
那么回滚莫队是什么呢? 其实就是莫队 + 栈.我们把加入数字时每一个影响都加入到栈中, 然后回滚的时候弹栈消除影响.
首先, 对于左右区间在同一个快内的询问直接暴力查就好了.
然后考虑怎么加入一个数的影响. 直接并查集合并就好了, 维护一个集合的大小(siz).
我们找出左端点在同一个块(x)内的询问. 对于右端点, 我们已经排好序了, 是递增的, 所以只存在加入操作.对于左端点, 我们每次把左指针都回滚块(x)的右端点, 然后左移左端点, 也是只有加入操作.
总结一下 : 右端点不断向右移, 左端点总是左移,回滚,左移,回滚......由于左端点所在块的大小为(sqrt n)的, 所以和一般莫队相比也只是乘上了2的常数.总的复杂度为(O(n sqrt n alpha)).有一个并查集的常数.
#include <bits/stdc++.h>
#define rei register int
using namespace std;
inline int read() {
int s = 0, f = 1; char ch;
while(!isdigit(ch = getchar())) (ch == '-') && (f = -f);
for(s = ch ^ 48;isdigit(ch = getchar()); s = (s << 1) + (s << 3) + (ch ^ 48));
return s * f;
}
const int N = 5e4 + 5, M = 250;
int n, m, top, res, top1;
int a[N], L[N], R[N], fa[N], siz[N], vis[N], pos[N], ans[N], tnp[M];
struct ques { int l, r, id; } q[N];
struct stack { int opt, x, y; } sta[N << 3], sta1[N << 3];
int cmp(ques x, ques y) {
return pos[x.l] == pos[y.l] ? x.r < y.r : pos[x.l] < pos[y.l];
}
int find(int x) {
return x == fa[x] ? x : find(fa[x]);
}
void add(int x) {
vis[a[x]] = 1;
sta1[++ top1].opt = 1; sta1[top1].x = a[x];
if(vis[a[x] - 1]) {
int fx = find(a[x]), fy = find(a[x] - 1);
if(siz[fy] < siz[fx]) swap(fx, fy);
sta1[++ top1].opt = 2; sta1[top1].x = fx;
fa[fx] = fy;
sta1[++ top1].opt = 3; sta1[top1].x = fy; sta1[top1].y = siz[fy];
siz[fy] += siz[fx];
res = max(res, siz[fy]);
// cout << a[x] - 1 << " " << fy << " " << siz[fy] << "^^^
";
}
if(vis[a[x] + 1]) {
int fx = find(a[x]), fy = find(a[x] + 1);
if(siz[fy] < siz[fx]) swap(fx, fy);
sta1[++ top1].opt = 2; sta1[top1].x = fx;
fa[fx] = fy;
sta1[++ top1].opt = 3; sta1[top1].x = fy; sta1[top1].y = siz[fy];
siz[fy] += siz[fx];
res = max(res, siz[fy]);
}
}
void add_(int x) {
// cout << a[x] << "------>
";
vis[a[x]] = 1;
sta[++ top].opt = 1; sta[top].x = a[x];
if(vis[a[x] - 1]) {
// cout << "---
";
int fx = find(a[x]), fy = find(a[x] - 1);
if(siz[fy] < siz[fx]) swap(fx, fy);
sta[++ top].opt = 2; sta[top].x = fx;
fa[fx] = fy;
sta[++ top].opt = 3; sta[top].x = fy; sta[top].y = siz[fy];
// cout << siz[fy] << " " << siz[fx] << ")))
";
siz[fy] += siz[fx];
res = max(res, siz[fy]);
// cout << res << "
";
}
if(vis[a[x] + 1]) {
// cout << "+++
";
int fx = find(a[x]), fy = find(a[x] + 1);
if(siz[fy] > siz[fx]) swap(fx, fy);
sta[++ top].opt = 2; sta[top].x = fx;
fa[fx] = fy;
sta[++ top].opt = 3; sta[top].x = fy; sta[top].y = siz[fy];
siz[fy] += siz[fx];
res = max(res, siz[fy]);
// cout << res << "
";
}
}
void Pop_() {
if(sta[top].opt == 0) res = sta[top].x, top --;
if(sta[top].opt == 1) vis[sta[top].x] = 0, top --;
if(sta[top].opt == 2) fa[sta[top].x] = sta[top].x, top --;
if(sta[top].opt == 3) siz[sta[top].x] = sta[top].y, top --;
}
void Pop() {
if(sta1[top1].opt == 0) res = sta1[top1].x, top1 --;
if(sta1[top1].opt == 1) vis[sta1[top1].x] = 0, top1 --;
if(sta1[top1].opt == 2) fa[sta1[top1].x] = sta1[top1].x, top1 --;
if(sta1[top1].opt == 3) siz[sta1[top1].x] = sta1[top1].y, top1 --;
}
int main() {
freopen("permu.in","r",stdin); freopen("permu.out","w",stdout);
n = read(); m = read(); int B = sqrt(n);
for(int i = 1;i <= n; i++) fa[i] = i, siz[i] = 1; res = 1;
for(int i = 1;i <= n; i++) L[i] = 2333333;
for(rei i = 1;i <= n; i++) a[i] = read(), pos[i] = (i - 1) / B + 1;
for(int i = 1;i <= n; i++) L[pos[i]] = min(L[pos[i]], i), R[pos[i]] = max(R[pos[i]], i);
for(rei i = 1;i <= m; i++) q[i].l = read(), q[i].r = read(), q[i].id = i;
// for(int i = 1;i <= n; i++) cout << pos[i] << " "; cout << "
";
sort(q + 1, q + m + 1, cmp);
// for(int i = 1;i <= m; i++) cout << q[i].l << " " << q[i].r << " " << q[i].id << "
";
rei x, y;
for(rei i = 1, j;i <= m; i = j) {
int now_k = pos[q[i].l];
x = R[now_k] + 1; y = R[now_k];
// cout << x << " " << y << " " << res << "
";
sta1[++ top1].opt = 0; sta1[top1].x = res;
for(j = i;pos[q[j].l] == now_k; j++) {
if(pos[q[j].r] == pos[q[j].l]) {
// cout << q[j].l << " " << q[j].r << "!!!
";
int cnt = 0;
for(int k = q[j].l;k <= q[j].r; k++) tnp[++ cnt] = a[k];
sort(tnp + 1, tnp + cnt + 1);
int cop = 1, cip = 1;
for(int k = 2;k <= cnt; k++)
if(tnp[k] == tnp[k - 1] + 1) cop ++, cip = max(cip, cop);
else cop = 1;
ans[q[j].id] = cip;
}
else {
// cout << q[j].l << " " << q[j].r << ")))
";
while(y < q[j].r) add(++ y);
// cout << j << ":" << res << "!!!
";
sta[++ top].opt = 0; sta[top].x = res;
while(x > q[j].l) add_(-- x);
ans[q[j].id] = res;
while(top) Pop_();
x = R[now_k] + 1;
}
}
while(top1) Pop();
}
for(rei i = 1;i <= m; i++) printf("%d
", ans[i]);
fclose(stdin); fclose(stdout);
return 0;
}
/*
8 3
3 1 7 2 5 8 6 4
1 4
5 8
1 7
*/
(上面代码写的太麻烦了, 其实好多东西都可以合并到一起去的).
T2
有一颗(n)个点的树, 有三个点在树上轮流取点,直到点被取完.
取完之后需要计算每一个人的得分.有(m)个幸运数, 对于一个点对((u, v)), 如果它们在原图上的距离为一个幸运数, 并且被同一个人取到, 那么这个人就得到一分.
假设每个人取点时都是等概率的选取一个未被选过的点, 问每个人得分的期望.
(n <= 5e4, m <= 10).
点分治.
(考场上完全没有思路啊)
对于每个人其实都是可以分开算的, 因为选取每个点的概率都是相等的.
假设一个人需要选择(k)个点, 那么总共就有(C_n^k)中方案, 对于某一个点对的选取概率是 : (frac{C_{n-2}^{k-2}}{C_n^k} = frac{k*(k-1)}{n*(n-1)}).意思就是当前的点对已经选好了, 剩下(k-2)个点随便选.
每一个点对的概率都是这样的, 所以现在问题转化成了, 计算有多少个点对的距离为幸运数, 那么直接点分治就好了, 复杂度(O(mnlogn)).
#include <bits/stdc++.h>
using namespace std;
inline long long read() {
long long s = 0, f = 1; char ch;
while(!isdigit(ch = getchar())) (ch == '-') && (f = -f);
for(s = ch ^ 48;isdigit(ch = getchar()); s = (s << 1) + (s << 3) + (ch ^ 48));
return s * f;
}
const int N = 5e4 + 5;
int n, m, rt, cnt, totsiz;
int k[11], dis[N], tmp[N], siz[N], num[N], vis[N], head[N], tong[N], Tmp[N], Tong[N], maxsiz[N];
struct edge { int to, nxt; } e[N << 1];
void add(int x, int y) {
e[++ cnt].nxt = head[x]; head[x] = cnt; e[cnt].to = y;
}
void get_rt(int x, int fa) {
siz[x] = 1; maxsiz[x] = 0;
for(int i = head[x]; i ; i = e[i].nxt) {
int y = e[i].to; if(vis[y] || y == fa) continue ;
get_rt(y, x); siz[x] += siz[y];
maxsiz[x] = max(maxsiz[x], siz[y]);
}
maxsiz[x] = max(maxsiz[x], totsiz - siz[x]);
if(maxsiz[rt] > maxsiz[x]) rt = x;
}
void get_dis(int x, int fa) {
if(!tong[dis[x]] && dis[x]) tmp[++ cnt] = dis[x];
tong[dis[x]] ++;
for(int i = head[x]; i ; i = e[i].nxt) {
int y = e[i].to; if(y == fa || vis[y]) continue ;
dis[y] = dis[x] + 1;
get_dis(y, x);
}
}
void calc(int x) {
// cout << x << "--------->
";
int Cnt = 0; dis[x] = 0;
for(int ii = head[x]; ii ; ii = e[ii].nxt) {
int y = e[ii].to; if(vis[y]) continue ;
dis[y] = dis[x] + 1;
for(int i = 1;i <= cnt; i++) tong[tmp[i]] = 0; cnt = 0;
get_dis(y, x);
sort(tmp + 1, tmp + cnt + 1);
// cout << y << "---->
";
// for(int i = 1;i <= cnt; i++) cout << tmp[i] << " "; cout << "
";
// for(int i = 1;i <= cnt; i++) cout << tong[tmp[i]] << " "; cout << "
";
for(int i = 1;i <= m; i++) {
num[i] += tong[k[i]];
for(int j = 1;j <= cnt; j++) {
if(tmp[j] >= k[i]) break ;
num[i] += tong[tmp[j]] * Tong[k[i] - tmp[j]];
}
// cout << i << ":" << num[i] << "
";
}
for(int i = 1;i <= cnt; i++) {
if(!Tong[tmp[i]]) Tmp[++ Cnt] = tmp[i];
Tong[tmp[i]] += tong[tmp[i]];
}
}
for(int i = 1;i <= Cnt; i++) Tong[tmp[i]] = tong[tmp[i]] = 0;
}
void solve(int x) {
calc(x); vis[x] = 1;
for(int i = head[x]; i ; i = e[i].nxt) {
int y = e[i].to; if(vis[y]) continue ;
maxsiz[0] = totsiz = siz[y]; rt = 0;
get_rt(y, 0); solve(rt);
}
}
int t[4];
void calc_sum(int x) {
double k_ = 1.0 * t[x] * (t[x] - 1) / (1.0 * n * (n - 1)), sum = 0;
for(int i = 1;i <= m; i++) sum += k_ * num[i];
printf("%.2lf
", sum);
}
int main() {
freopen("game.in","r",stdin); freopen("game.out","w",stdout);
n = read(); m = read();
for(int i = 1;i <= m; i++) k[i] = read();
for(int i = 1, x, y;i < n; i++) {
x = read(); y = read(); add(x, y); add(y, x);
}
maxsiz[0] = totsiz = n; rt = 0;
get_rt(1, 0); solve(rt);
// for(int i = 1;i <= m; i++) cout << i << ":" << k[i] << " " << num[i] << "
";
int t1 = n / 3, t2 = n % 3;
if(t2 == 0) t[1] = t[2] = t[3] = t1;
if(t2 == 1) t[1] = t1 + 1, t[2] = t[3] = t1;
if(t2 == 2) t[1] = t[2] = t1 + 1, t[3] = t1;
for(int i = 1;i <= 3; i++) calc_sum(i);
fclose(stdin); fclose(stdout);
return 0;
}
/*
5 3
1 2 3
1 2
1 5
2 3
2 4
*/
T3
给定一张(n)个点的无向图, 要求把(n)个点都分入(A, B)集合中.
若点(i, j)不相连, 则(i, j)不可以同时放入(A)集合中;
若点(i, j)相连,则(i, j)不可以同时放入(B)集合中;
(A, B)集合都不可以为空.
(相连是指有直接连边)
问有多少种方案可以满足上面的条件.
(n <= 5000).
2-sat.
一个点只能有两种选择, 显然2-sat.
如果两个点(x,y)相连, 那么连((x+n ightarrow y), (y+n ightarrow x)).
如果两个点(x, y)不相连, 那么连((x ightarrow y + n)(y ightarrow x +n)).
然后跑Tarjan判断有无解.
主要是怎么找到所有方案.
首先找到一个初始方案, 我们发现要得到其他方案只有这三种方式:
1.找一个(A)集合中的点(x)放到(B)集合, 条件是与(x)相连的所有点都不可以在(B)集合.
2.找一个(B)集合中的点(y)放到(A)集合, 条件是与(y)不相连的所有点都不可以在(A)集合.
3.找一个(A)集合中的点(x)和(B)集合中的点(y), 把它们交换, 条件是除了(y)所有与(x)相连的点都不可以在(B)集合, 除了(x)所有与(y)不相连的点都不可以在(A)集合.
为什么只有这三种情况呢? 假设我们要从(A)集合移动两个点到(B)集合, 那么这两个点一定是有直接连边的, 那么它们就不能同时存在于(B)集合.所以不管移动那个集合, 一定是只能移动一个点的.
记得最后判断一下(A, B)不为空就好了.
#include <bits/stdc++.h>
using namespace std;
inline long long read() {
long long s = 0, f = 1; char ch;
while(!isdigit(ch = getchar())) (ch == '-') && (f = -f);
for(s = ch ^ 48;isdigit(ch = getchar()); s = (s << 1) + (s << 3) + (ch ^ 48));
return s * f;
}
const int N = 5005, M = 3e7;
int n, cnt, tot, top, css, siz1, siz2;
int in[N << 1], col[N << 1], dfn[N << 1], sta[N << 1], low[N << 1], head[N << 1], inse[N], link[N][N];
struct edge { int to, nxt; } e[M];
vector <int> v[N];
void add(int x, int y) {
e[++ cnt].nxt = head[x]; head[x] = cnt; e[cnt].to = y;
}
void Tarjan(int x) {
dfn[x] = low[x] = ++ tot; in[x] = 1; sta[++ top] = x;
for(int i = head[x]; i ; i = e[i].nxt) {
int y = e[i].to;
if(!dfn[y]) Tarjan(y), low[x] = min(low[x], low[y]);
else if(in[y]) low[x] = min(low[x], dfn[y]);
}
if(dfn[x] == low[x]) {
int p; css ++;
do {
in[p = sta[top --]] = 0;
col[p] = css;
} while(p != x);
}
}
void Sta_pro() {
int ans;
if(siz1 == 0 || siz2 == 0) ans = 0; // ***
else ans = 1;
for(int x = 1;x <= n; x++) {
if(!inse[x] && siz1 > 1) { // ***
int f = 0;
for(int i = 0;i < (int) v[x].size(); i++)
if(inse[v[x][i]]) { f = 1; break; }
if(!f) ans ++;
}
if(inse[x] && siz2 > 1) { // ***
int f = 0;
for(int y = 1;y <= n; y++) {
if(y == x || link[x][y]) continue ;
if(!inse[y]) { f = 1; break ; }
}
if(!f) ans ++;
}
}
for(int x = 1;x <= n; x++)
for(int y = 1;y <= n; y++)
if(!inse[x] && inse[y]) {
int f = 0;
for(int i = 0;i < (int) v[x].size(); i++)
if(inse[v[x][i]] && v[x][i] != y) { f = 1; break ; }
if(f) continue ;
for(int z = 1;z <= n; z++) {
if(z == x || z == y || link[z][y]) continue ;
if(!inse[z]) { f = 1; break ; }
}
if(!f) ans ++;
}
printf("%d", ans);
}
void Work2() {
for(int i = 1;i <= n; i++)
for(int j = i + 1;j <= n; j++)
if(link[i][j]) add(i + n, j), add(j + n, i);
else add(i, j + n), add(j, i + n);
for(int i = 1;i <= 2 * n; i++) if(!dfn[i]) Tarjan(i);
// for(int i = 1;i <= n; i++) cout << i << ":" << col[i] << " " << col[i + n] << "
";
for(int i = 1;i <= n; i++) if(col[i] == col[i + n]) {
printf("0"); return ;
}
for(int i = 1;i <= n; i++) inse[i] = col[i] < col[i + n] ? 0 : 1;
for(int i = 1;i <= n; i++)
if(!inse[i]) siz1 ++;
else siz2 ++;
// for(int i = 1;i <= n; i++) cout << i << ":" << inse[i] << "
";
Sta_pro();
}
int main() {
freopen("conspiracy.in","r",stdin); freopen("conspiracy.out","w",stdout);
n = read();
for(int i = 1, t;i <= n; i++) {
t = read();
for(int j = 1, x;j <= t; j++)
x = read(), v[i].push_back(x), link[i][x] = 1;
}
Work2();
fclose(stdin); fclose(stdout);
return 0;
}
/*
4
2 2 3
2 1 3
3 1 2 4
1 3
*/