2022NOIPA层联测6
设密码比较失败,所以,
A. 构造字符串(str)
并查集维护一下相同的位置,注意到$ LCP + 1 $ 位置不同,于是每个集合取出来最靠前的为代表,两个集合不同,大集合向小集合连边,每次集合复制为能扫到的 \(mex\)
code
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<map>
#include<set>
#include<queue>
#include<vector>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
int read(){
int x = 0; char c = getchar();
while(!isdigit(c))c = getchar();
do{x = x * 10 + (c ^ 48); c = getchar();}while(isdigit(c));
return x;
}
const int maxn = 10005;
int f[maxn];
int fa(int x){return f[x] == x ? x : f[x] = fa(f[x]);}
void merge(int x, int y){
x = fa(x); y = fa(y);
if(x > y)swap(x, y);
f[y] = x;
}
int n, m;
int ans[maxn];
struct opt{int x, y, z;}op[maxn];
int head[maxn], tot;
struct edge{int to, net;}e[maxn << 1 | 1];
void add(int u, int v){
e[++tot].net = head[u];
head[u] = tot;
e[tot].to = v;
}
bool vis[maxn];
int main(){
n = read(), m = read();
for(int i = 1; i <= n; ++i)f[i] = i;
for(int i = 1; i <= m; ++i){
int x = read(), y = read(), z = read();
for(int j = 0; j < z; ++j)merge(x + j, y + j);
op[i].x = x; op[i].y = y; op[i].z = z;
}
for(int i = 1; i <= m; ++i){
int u = fa(op[i].x + op[i].z), v = fa(op[i].y + op[i].z);
if(u <= n && v <= n && u && v){
if(fa(u) == fa(v)){
printf("-1\n");
return 0;
}
add(max(u, v), min(u, v));
}
}
for(int x = 1; x <= n; ++x)if(fa(x) == x){
for(int i = head[x]; i; i = e[i].net){
int v = e[i].to;
if(ans[v] > -1)
vis[ans[v]] = 1;
}
ans[x] = 0;
while(vis[ans[x]])++ans[x];
for(int i = head[x]; i; i = e[i].net){
int v = e[i].to;
vis[ans[v]] = 0;
}
}
for(int i = 1; i <= n; ++i)printf("%d ",ans[fa(i)]);
return 0;
}
B. 寻宝(treasure)
并查集维护整块,传送门加有向边,询问变成能否从某个点到另外一个点
发现边数很少,于是直接 \(DFS\)
如果边数增多,好像就需要 \(tarjan\) 缩点, 然后 \(bitset\) 优化
code
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<map>
#include<set>
#include<queue>
#include<vector>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
int read(){
int x = 0; char c = getchar();
while(!isdigit(c))c = getchar();
do{x = x * 10 + (c ^ 48); c = getchar();}while(isdigit(c));
return x;
}
const int maxn = 55000;
int n, m, k, q;
char c[maxn], s[maxn];
int head[maxn], tot;
struct edge{int to, net;}e[maxn << 1 | 1];
void add(int u, int v){
e[++tot].net = head[u];
head[u] = tot;
e[tot].to = v;
}
int f[maxn];
int fa(int x){return f[x] == x ? x : f[x] = fa(f[x]);}
void merge(int x, int y){
x = fa(x); y = fa(y);
if(x != y)f[y] = x;
}
int id(int x, int y){return (x - 1) * m + y;}
bool vis[maxn];
int sta[maxn], top;
int check(int x, int y){
if(x == y)return true;
vis[x] = 1; sta[++top] = x;
for(int i = head[x]; i; i = e[i].net){
int v = e[i].to;
if(vis[v])continue;
if(v == y)return true;
if(check(v, y))return true;
}
return false;
}
int main(){
n = read(), m = read(), k = read(), q = read();
for(int i = 1; i <= n * m; ++i)f[i] = i;
for(int i = 1; i <= n; ++i){
scanf("%s",c + 1);
for(int j = 1; j <= m; ++j)s[id(i, j)] = c[j];
if(i != 1){
for(int j = 1; j <= m; ++j)if(c[j] == '.' && s[id(i - 1, j)] == '.')merge(id(i, j), id(i - 1, j));
}
for(int j = 2; j <= m; ++j)if(c[j] == '.' && c[j - 1] == '.')merge(id(i, j - 1), id(i, j));
}
for(int i = 1; i <= k; ++i){
int a = read(), b = read(), c = read(), d = read();
if(s[id(a, b)] == '#' || s[id(c, d)] == '#' || fa(id(a, b)) == fa(id(c, d)))continue;
add(fa(id(a, b)), fa(id(c, d)));
}
for(int i = 1; i <= q; ++i){
int a = read(), b = read(), c = read(), d = read();
printf("%d\n",check(fa(id(a, b)), fa(id(c, d))));
while(top > 0)vis[sta[top--]] = false;
}
return 0;
}
C. 序列(seq)
学习了李超线段树,学长讲过但是从来没打过。。
前缀和一下,所求为 \(sa_r - sa_{l - 1} - k (sb_r - sb_{l - 1})[l <= p <= r]\)
拆成
\(sa_r - k sb_r[r >= p]\)
和 \(-sa_{l - 1} + k sb_{l - 1}[l <= p]\)
上面是两个一次函数的形式,于是用类似扫描线的方法用李超线段树维护一下直线即可
code
#pragma GCC optimize(3)
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<map>
#include<set>
#include<queue>
#include<vector>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
ll read(){
ll x = 0;bool f = 0; char c = getchar();
while(!isdigit(c)){if(c == '-')f = 1;c = getchar();}
do{x = x * 10 + (c ^ 48); c = getchar();}while(isdigit(c));
return f ? -x : x;
}
const int maxn = 1000005;
const int lim = 1000000;
int n, m;
ll a[maxn], b[maxn], suma[maxn], sumb[maxn], p, k;
struct lct{
struct node{
ll k, b;
node(){b = -0x3f3f3f3f3f3f3f3f;k = 0;}
}d[maxn << 3 | 1];
ll calc(ll x, ll k, ll b){return k * x + b;}
void insert(int x, int l, int r, ll k, ll b){
int mid = (l + r) >> 1;
ll v1 = calc(mid, k, b), v2 = calc(mid, d[x].k, d[x].b);
if(v1 > v2){
swap(k, d[x].k);
swap(b, d[x].b);
}
if(l == r)return;
if(calc(l, k, b) > calc(l, d[x].k, d[x].b))insert(x << 1, l, mid, k, b);
if(calc(r, k, b) > calc(r, d[x].k, d[x].b))insert(x << 1 | 1, mid + 1, r, k, b);
}
ll query(int x, int l, int r, int pos){
ll ans = calc(pos, d[x].k, d[x].b);
if(l == r)return ans;
int mid = (l + r) >> 1;
if(pos <= mid)ans = max(ans, query(x << 1, l, mid, pos));
else ans = max(ans, query(x << 1 | 1, mid + 1, r, pos));
return ans;
}
}t1;
struct query{
ll p, k; int id;
friend bool operator < (const query &x, const query &y){
return x.p < y.p;
}
}q[maxn];
ll ans[maxn];
int main(){
n = read(), m = read();
for(int i = 1; i <= n; ++i)a[i] = read(), b[i] = read();
for(int i = 1; i <= n; ++i)suma[i] = suma[i - 1] + a[i];
for(int i = 1; i <= n; ++i)sumb[i] = sumb[i - 1] + b[i];
for(int i = 1; i <= m; ++i)q[i].p = read(), q[i].k = read(), q[i].id = i;
sort(q + 1, q + m + 1);
int ins = 1;
t1.insert(1, -lim, lim, 0, 0);
for(int i = 1; i <= m; ++i){
while(ins <= n && ins + 1 <= q[i].p)t1.insert(1, -lim, lim, sumb[ins], -suma[ins]), ++ins;
ans[q[i].id] += t1.query(1, -lim, lim, q[i].k);
}
for(int i = 1; i <= maxn << 3; ++i)t1.d[i].k = 0, t1.d[i].b = -0x3f3f3f3f3f3f3f3f;
ins = n;
for(int i = m; i >= 1; --i){
while(ins >= 1 && ins >= q[i].p)t1.insert(1, -lim, lim, -sumb[ins], suma[ins]), --ins;
ans[q[i].id] += t1.query(1, -lim, lim, q[i].k);
}
for(int i = 1; i <= m; ++i)printf("%lld\n",ans[i]);
return 0;
}
D. 构树(tree)
一个 \(n\) 个点, \(cnt\) 个联通块的森林的生成树的个数为 \(n^{cnt - 2}\Pi size_i\)
考虑用 \(g_i\) 表示至少有 \(i\) 条边与原树相同的方案数
考虑通过 \(DP\) 求出 \(\Pi size_i\)
\(f_{i, j, 1 / 0}\) 表示以 \(i\) 为根的子树内,有 \(j\) 个联通块, \(i\) 所在的联通块是否选出了一个元素
\(1/ 0\)的关键在于一个联通块恰好从 \(size_i\) 个点每个点贡献一次
转移用类似树形背包的方法,考虑是否选择当前根与儿子的边
于是 \(g_i = n^{n - i - 2}f_{1, n - i, 1}\)
然后二项式反演一下(我不会,直接褐)
code
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<map>
#include<set>
#include<queue>
#include<vector>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
int read(){
int x = 0; char c = getchar();
while(!isdigit(c))c = getchar();
do{x = x * 10 + (c ^ 48); c = getchar();}while(isdigit(c));
return x;
}
const int maxn = 8005;
const int mod = 1e9 + 7;
int qpow(int x, int y){
if(y <= 0)return 1;
int ans = 1;
for(; y; y >>= 1, x = 1ll * x * x % mod)if(y & 1)ans = 1ll * ans * x % mod;
return ans;
}
int n, head[maxn], tot, fac[maxn], inv[maxn];
struct edge{int to, net;}e[maxn << 1 | 1];
void add(int u, int v){
e[++tot].net = head[u];
head[u] = tot;
e[tot].to = v;
}
int si[maxn];
vector<int>f[maxn][2];
int tmp[maxn][2];
void add(int &x, ll y){x += y; x = x >= mod ? x - mod : x;}
void dfs(int x, int fa){
for(int i = head[x]; i; i = e[i].net){
int v = e[i].to;
if(v == fa)continue;
dfs(v, x);
}
f[x][0].resize(2);f[x][1].resize(2);
f[x][0][1] = f[x][1][1] = 1; si[x] = 1;
for(int i = head[x]; i; i = e[i].net){
int v = e[i].to;
if(v == fa)continue;
f[x][0].resize(si[x] + 1 + si[v]);
f[x][1].resize(si[x] + 1 + si[v]);
for(int j = 1; j <= si[x] + si[v]; ++j)tmp[j][1] = 0, tmp[j][0] = 0;
for(int j = 1; j <= si[x]; ++j)
for(int k = 1; k <= si[v]; ++k){
add(tmp[j + k][0], 1ll * f[v][1][k] * f[x][0][j] % mod);
add(tmp[j + k][1], 1ll * f[v][1][k] * f[x][1][j] % mod);
add(tmp[j + k - 1][0], 1ll * f[v][0][k] * f[x][0][j] % mod);
add(tmp[j + k - 1][1], (1ll * f[v][0][k] * f[x][1][j] % mod + 1ll * f[v][1][k] * f[x][0][j] % mod) % mod);
}
si[x] += si[v];
for(int j = 1; j <= si[x]; ++j)f[x][1][j] = tmp[j][1], f[x][0][j] = tmp[j][0];
}
}
int c(int n, int m){
if(n < 0 || m < 0 || n < m)return 1;
return 1ll * fac[n] * inv[m] % mod * inv[n - m] % mod;
}
int g[maxn];
int ans[maxn];
int main(){
n = read();
for(int i = 1; i < n; ++i){
int u = read(), v = read();
add(u, v); add(v, u);
}
dfs(1, 1);
fac[0] = inv[0] = 1; for(int i = 1; i <= n; ++i)fac[i] = 1ll * fac[i - 1] * i % mod;
inv[n] = qpow(fac[n], mod - 2); for(int i = n - 1; i >= 1; --i)inv[i] = 1ll * inv[i + 1] * (i + 1) % mod;
for(int i = 0; i < n - 1; ++i)g[i] = 1ll * qpow(n, n - i - 2) * f[1][1][n - i] % mod;
g[n - 1] = 1;
for(int i = 0; i < n; ++i){
for(int j = i; j < n; ++j)
if((j - i) & 1)add(ans[i], mod - 1ll * c(j, i) * g[j] % mod);
else add(ans[i], 1ll * c(j, i) * g[j] % mod);
}
for(int i = 0; i < n; ++i)printf("%d ",ans[i]);
return 0;
}