专栏文章
省队集训-0420模拟赛
个人记录参与者 1已保存评论 0
文章操作
快速查看文章及其快照的属性,并进行相关操作。
- 当前评论
- 0 条
- 当前快照
- 1 份
- 快照标识符
- @mipjm2nk
- 此快照首次捕获于
- 2025/12/03 13:04 3 个月前
- 此快照最后确认于
- 2025/12/03 13:04 3 个月前
A.最小生成树
题意:
个点, 条边,每条边有两个边权 ,可以选一个定为该边边权。对于每个 ,求恰好选 个 , 个 时最大的最小生成树。
。
。
Sol:
Kruskal。
将 条边从小到大排序,设 表示已经考虑到第 条边,已选 个 ,并查集状态为 时的最大值。
考虑怎么转移。
每一条边有两个分边,在较小的那条时确定选 。
将 条边从小到大排序,设 表示已经考虑到第 条边,已选 个 ,并查集状态为 时的最大值。
考虑怎么转移。
每一条边有两个分边,在较小的那条时确定选 。
如果 已在一个连通块内:
如果这条边是第一个分边,则 可以加一,否则 只能不加;
如果这条边是第一个分边,则 可以加一,否则 只能不加;
如果 不在一个连通块内:
如果这条边是第一个分边,则可以选择加入/不加入生成树, 加上对应的贡献,否则只能加入生成树。
如果这条边是第一个分边,则可以选择加入/不加入生成树, 加上对应的贡献,否则只能加入生成树。
用个 map 即可,细节见代码。
CPP#include <bits/stdc++.h>
using namespace std;
const int N = 10,M = 210;
int n,m,f[N],g[N],a[M],b[M];
struct Edge{int x,y,z,t,id;} e[M];
bool cmp(Edge x,Edge y)
{
if(x.z == y.z) return x.t < y.t;
return x.z < y.z;
}
unordered_map <int,int> dp[M][M / 2];
void get_t(int s)
{
for(int i = n;i >= 1;i--) g[i] = s % 10,s /= 10;
}
int get_hsh()
{
int s = 0;
for(int i = 1;i <= n;i++) s = s * 10 + f[i];
return s;
}
void merge(int x,int y)
{
if(x > y) swap(x,y);
for(int i = 1;i <= n;i++) if(f[i] == y) f[i] = x;
}
int main()
{
freopen("mst.in","r",stdin);
freopen("mst.out","w",stdout);
scanf("%d%d",&n,&m);
for(int i = 1,x,y;i <= m;i++)
{
scanf("%d%d%d%d",&x,&y,&a[i],&b[i]);
e[i] = (Edge){x,y,a[i],0,i},e[i + m] = (Edge){x,y,b[i],1,i};
}
sort(e + 1,e + 2 * m + 1,cmp);
for(int i = 1;i <= n;i++) f[i] = i;
dp[0][0][get_hsh()] = 0;
for(int i = 1,x,y,z,t,fl;i <= 2 * m;i++)
{
x = e[i].x,y = e[i].y,z = e[i].z,t = e[i].t,fl = (t == (a[e[i].id] > b[e[i].id]));
for(int j = 0;j <= m;j++)
{
for(pair <int,int> p : dp[i - 1][j])
{
int s1 = p.first,s2,v = p.second;
get_t(s1);
if(g[x] == g[y])
{
dp[i][j][s1] = max(dp[i][j][s1],v);
if(fl) dp[i][j + 1][s1] = max(dp[i][j + 1][s1],v);
continue;
}
if(fl) dp[i][j + e[i].t][s1] = max(dp[i][j + t][s1],v);
memcpy(f,g,sizeof(f));
merge(f[x],f[y]);
s2 = get_hsh();
if(fl) dp[i][j + (t ^ 1)][s2] = max(dp[i][j + (t ^ 1)][s2],v + z);
else dp[i][j][s2] = max(dp[i][j][s2],v + z);
}
}
}
for(int i = 1;i <= n;i++) f[i] = 1;
int s = get_hsh();
for(int i = 0;i <= m;i++) printf("%d\n",dp[2 * m][i][s]);
return 0;
}
B.操作
题意:
给出 个操作以及模数 。
有一个初始为 的变量 ,操作 :;操作 :。
可以以任意顺序操作,问 中有多少个数无论如何都不能在最后得到。
。
有一个初始为 的变量 ,操作 :;操作 :。
可以以任意顺序操作,问 中有多少个数无论如何都不能在最后得到。
。
Sol:
等价于在集合 中选一个数,在集合 中选若干个数(可不选),把它们乘起来。
用一个 的原根 表示所有数,则乘法转为加法,变成卷积形式。初始时令答案集合为 集合,依次卷上 集合中的每个数,可以用 bitset。
发现每个数只会加入 次,即更新次数 。
于是想到分治+哈希。设我们正在加入 ,当区间 和 的状态不一样时我们才递归进去。然后用树状数组维护哈希。
证明一下这样的次数是对的:
注意到 会形成若干个环,而环上相邻位置状态为 的数量是等于 的数量的,因此总的递归到最底层的次数还是 的。每次递归到最底层会走 层,然后每层树状数组求哈希 ,所以总复杂度为 。
用一个 的原根 表示所有数,则乘法转为加法,变成卷积形式。初始时令答案集合为 集合,依次卷上 集合中的每个数,可以用 bitset。
发现每个数只会加入 次,即更新次数 。
于是想到分治+哈希。设我们正在加入 ,当区间 和 的状态不一样时我们才递归进去。然后用树状数组维护哈希。
证明一下这样的次数是对的:
注意到 会形成若干个环,而环上相邻位置状态为 的数量是等于 的数量的,因此总的递归到最底层的次数还是 的。每次递归到最底层会走 层,然后每层树状数组求哈希 ,所以总复杂度为 。
Code:
CPP#include <bits/stdc++.h>
#define pb push_back
#define lb(x) ((x) & (-x))
using namespace std;
const int N = 1e6 + 5,K = 131,mod = 200002333;
int T,n,p,g,tot,fl1,fl2,ans,f[N],prm[N],id[N],mi[N],imi[N],vis[N];
vector <int> a,b,d,pos;
struct Bit
{
int s[N];
void update(int x,int v){for(int i = x;i < p;i += lb(i)) s[i] = (s[i] + v) % mod;}
int query(int x)
{
int ret = 0;
for(int i = x;i;i -= lb(i)) ret = (ret + s[i]) % mod;
return ret;
}
} tr;
int ksm(int x,int y,int mod)
{
int ret = 1;
while(y)
{
if(y & 1) ret = 1LL * ret * x % mod;
x = 1LL * x * x % mod,y >>= 1;
}
return ret;
}
bool check(int x)
{
for(int i : d) if(ksm(x,(p - 1) / i,p) == 1) return false;
return true;
}
int get_hsh(int l,int r){return 1LL * (tr.query(r + 1) - tr.query(l) + mod) * imi[l] % mod;}
bool eql(int l,int r,int v)
{
int tl = l + v,tr = r + v;
if(tr < p - 1) return get_hsh(l,r) == get_hsh(tl,tr);
if(tl < p - 1 && p - 1 <= tr) return get_hsh(l,l + p - 2 - tl) == get_hsh(tl,p - 2) && get_hsh(l + p - 1 - tl,r) == get_hsh(0,tr - p + 1);
return get_hsh(l,r) == get_hsh(tl - p + 1,tr - p + 1);
}
void solve(int l,int r,int v)
{
if(l == r)
{
if(vis[l]) pos.pb((l + v) % (p - 1));
return ;
}
int mid = (l + r) / 2;
if(!eql(l,mid,v)) solve(l,mid,v);
if(!eql(mid + 1,r,v)) solve(mid + 1,r,v);
}
int main()
{
freopen("operation.in","r",stdin);
freopen("operation.out","w",stdout);
for(int i = 2;i < N;i++)
{
if(!f[i]) prm[++tot] = i;
for(int j = 1;j <= tot && i * prm[j] < N;j++)
{
f[i * prm[j]] = 1;
if(i % prm[j] == 0) break;
}
}
mi[0] = imi[0] = 1,imi[1] = ksm(K,mod - 2,mod);
for(int i = 1;i < N;i++) mi[i] = 1LL * mi[i - 1] * K % mod;
for(int i = 1;i < N;i++) imi[i] = 1LL * imi[i - 1] * imi[1] % mod;
scanf("%d",&T);
while(T--)
{
fl1 = fl2 = 0;
a.clear(),b.clear(),d.clear();
scanf("%d%d",&p,&n);
for(int i = 1;i <= tot;i++) if((p - 1) % prm[i] == 0) d.pb(prm[i]);
for(g = 1;!check(g);g++);
for(int i = 0,mi = 1;i < p - 1;i++,mi = 1LL * mi * g % p) id[mi] = i;
for(int i = 1,op,x;i <= n;i++)
{
scanf("%d%d",&op,&x);
if(!x)
{
if(!op) fl1 = 1;
else fl2 = 1;
continue;
}
if(!op) a.pb(id[x]);
else b.pb(id[x]);
}
if(!a.size())
{
printf("%d\n",p - 1);
continue;
}
memset(tr.s,0,sizeof(tr.s));
for(int i = 0;i < p;i++) vis[i] = 0;
for(int i : a) if(!vis[i]) vis[i] = 1,tr.update(i + 1,mi[i]);
for(int i : b)
{
if(eql(0,p - 2,i)) continue;
pos.clear();
solve(0,p - 2,i);
for(int j : pos) vis[j] = 1,tr.update(j + 1,mi[j]);
}
ans = (!fl1 && !fl2);
for(int i = 0;i < p - 1;i++) ans += (!vis[i]);
printf("%d\n",ans);
}
return 0;
}
C.排列
题意:
P9265
给出一个排列 ,生成一个 个点的无向图,对于 ,它们之间有边,当且仅当 ,长度为 。
对于每个 问 ,如果不联通则 。
。
给出一个排列 ,生成一个 个点的无向图,对于 ,它们之间有边,当且仅当 ,长度为 。
对于每个 问 ,如果不联通则 。
。
Sol:
首先,如果 ,则称 为关键点,相邻的两个关键点 之间是一个极大连通块,证明考虑从这个区间的极大值处断开,然后归纳。
其次,将排列视为平面上的 个点 ,则 的点是平面左下角一个矩形或右上角一个矩形内,这个矩形的边界由上一次我们走到的最左、最下/最右、最上的点决定,可以归纳证明。
然后发现这两个矩形其实是独立的,因为我们总是用上次最左的点更新现在最下的点,用上次最下的点更新这次最左的点。
所以左下、右上角是等价的。下面只考虑左下角怎么做,右上角把整个平面旋转 度即可。
设从 出发,走了 步后,能到达的最做的点为 ,最下的点为 ,则 。
设 表示满足 且 的最小的 , 表示满足 且 的、同时 最小的 ,则 下一步会走到 。然后 的答案就是从 一直跳,经过的所有点的左下方的点数之和。
不难发现可以由跳的过程形成由向根树构成的森林。有结论:所有点经过的 总个数是 的,证明如下:
对于每个 ,将所有 按 升序排序,得到 ,称 的 为好的点,其它的点为坏的点。
注意到对于所有点 ,都有 。所以,如果在跳的过程中我们经过了某个坏的点 ,则下一步 至少会走到 ,即 至少减少 。由于一次跳的过程中 不增,所以一次至多经过 个坏点,总个数 。而好点个数不超过 ,所以树的点数为 的。
考虑怎么建树。显然可以暴力跳,然后用 set 之类的维护点集,但这样会有个 。
发现 随 的减小而减小。每个 维护一个栈 表示待加入点集的点,点按 降序排序。从大到小扫 ,将栈中的点加入点集,加入 时再将点 插入栈 里面。由于我们从达到小扫 ,所以 一定是最小的,和 的栈顶比较一下是否相同即可。这样可以得到点集和所有点的父亲。
查询的话,从小到大扫 ,每个点的权值等于其父亲的权值加上自己左下方的点数。查询左下方的点数时,由于有 次查询 次插入所以用分块平衡一下复杂度。 的答案就是 的权值。
一些细节:
1.最前面有说 是一个极大联通快,因此经过一个关键点时要清空分块。
2.我们只算了 的贡献,所以及的加上 的贡献,即 。
其次,将排列视为平面上的 个点 ,则 的点是平面左下角一个矩形或右上角一个矩形内,这个矩形的边界由上一次我们走到的最左、最下/最右、最上的点决定,可以归纳证明。
然后发现这两个矩形其实是独立的,因为我们总是用上次最左的点更新现在最下的点,用上次最下的点更新这次最左的点。
所以左下、右上角是等价的。下面只考虑左下角怎么做,右上角把整个平面旋转 度即可。
设从 出发,走了 步后,能到达的最做的点为 ,最下的点为 ,则 。
设 表示满足 且 的最小的 , 表示满足 且 的、同时 最小的 ,则 下一步会走到 。然后 的答案就是从 一直跳,经过的所有点的左下方的点数之和。
不难发现可以由跳的过程形成由向根树构成的森林。有结论:所有点经过的 总个数是 的,证明如下:
对于每个 ,将所有 按 升序排序,得到 ,称 的 为好的点,其它的点为坏的点。
注意到对于所有点 ,都有 。所以,如果在跳的过程中我们经过了某个坏的点 ,则下一步 至少会走到 ,即 至少减少 。由于一次跳的过程中 不增,所以一次至多经过 个坏点,总个数 。而好点个数不超过 ,所以树的点数为 的。
考虑怎么建树。显然可以暴力跳,然后用 set 之类的维护点集,但这样会有个 。
发现 随 的减小而减小。每个 维护一个栈 表示待加入点集的点,点按 降序排序。从大到小扫 ,将栈中的点加入点集,加入 时再将点 插入栈 里面。由于我们从达到小扫 ,所以 一定是最小的,和 的栈顶比较一下是否相同即可。这样可以得到点集和所有点的父亲。
查询的话,从小到大扫 ,每个点的权值等于其父亲的权值加上自己左下方的点数。查询左下方的点数时,由于有 次查询 次插入所以用分块平衡一下复杂度。 的答案就是 的权值。
一些细节:
1.最前面有说 是一个极大联通快,因此经过一个关键点时要清空分块。
2.我们只算了 的贡献,所以及的加上 的贡献,即 。
Code:
CPP#include <bits/stdc++.h>
#define pii pair <int,int>
#define fi first
#define se second
#define mkp make_pair
#define pb push_back
#define lb(x) ((x) & (-(x)))
using namespace std;
typedef long long ll;
const int N = 2e5 + 5,M = 5e7 + 5;
int n,cnt,p[N],ls[N],rs[N],fa[M],id[N],inv[N];
ll ans[N],sum[M];
vector <pii> v[N],tmp;
struct Bit
{
int minn[N];
void init(){memset(minn,127,sizeof(minn));}
void update(int x,int v){for(int i = x;i <= n;i += lb(i)) minn[i] = min(minn[i],v);}
int query(int x)
{
int ret = 2e9;
for(int i = x;i;i -= lb(i)) ret = min(ret,minn[i]);
return ret;
}
} tr;
struct Block
{
int siz,cnt,id[N],L[N],R[N],v[N],sum[N];
void init()
{
siz = (int)sqrt(n);
for(int i = 1;i <= n;i++) v[i] = 0;
for(int i = 1,l = 1,r = siz;l <= n;i++,l += siz,r += siz)
{
L[i] = l,R[i] = min(n,r),sum[i] = 0;
for(int j = L[i];j <= R[i];j++) id[j] = i;
}
cnt = id[n];
}
void update(int x,int k)
{
for(int i = x;i <= R[id[x]];i++) v[i] += k;
for(int i = id[x] + 1;i <= cnt;i++) sum[i] += k;
}
int query(int x){return sum[id[x]] + v[x];}
} B;
void solve(int t)
{
tr.init();
for(int i = 1;i <= n;i++)
{
tr.update(n - p[i] + 1,i);
ls[i] = tr.query(n - p[i] + 1);
}
for(int i = 1;i <= n;i++) inv[p[i]] = i;
for(int i = n,minn = n;i >= 1;i--)
{
minn = min(minn,p[i]);
rs[i] = inv[minn];
}
cnt = 0;
for(int i = 1;i <= n;i++) v[i].clear();
for(int i = n,x,y,cur;i >= 1;i--)
{
tmp.clear(),cur = 0;
while(cur < (int)v[i].size() && p[v[i][cur].fi] > p[i]) tmp.pb(v[i][cur++]);
tmp.pb(mkp(i,id[i] = ++cnt));
while(cur < (int)v[i].size()) tmp.pb(v[i][cur++]);
v[i] = tmp;
for(int t = 0;t < (int)v[i].size();t++)
{
pii j = v[i][t];
x = ls[j.fi],y = rs[i];
if(x == i && y == j.fi)
{
fa[j.se] = 0;
continue;
}
if(v[x].size() && v[x].back().fi == y) fa[j.se] = v[x].back().se;
else v[x].pb(mkp(y,fa[j.se] = ++cnt));
}
}
B.init();
for(int i = 1,lst = 0,maxn = 0;i <= n;i++)
{
for(int t = v[i].size() - 1;t >= 0;t--)
{
pii j = v[i][t];
sum[j.se] = sum[fa[j.se]] + B.query(p[j.fi]);
}
B.update(p[i],1);
ans[i] += sum[id[i]];
maxn = max(maxn,p[i]);
if(maxn == i)
{
for(int j = lst + 1;j <= i;j++) B.update(p[j],-1);
if(t) for(int j = lst + 1;j <= i;j++) ans[j] += i - lst - 1;
lst = i;
}
}
}
int main()
{
freopen("permutation.in","r",stdin);
freopen("permutation.out","w",stdout);
scanf("%d",&n);
for(int i = 1;i <= n;i++) scanf("%d",&p[i]);
solve(0);
for(int i = 1;i <= n;i++) p[i] = n - p[i] + 1;
reverse(p + 1,p + n + 1);
reverse(ans + 1,ans + n + 1);
solve(1);
for(int i = n;i >= 1;i--) printf("%lld ",ans[i]);
puts("");
return 0;
}
相关推荐
评论
共 0 条评论,欢迎与作者交流。
正在加载评论...