专栏文章

省队集训-0420模拟赛

个人记录参与者 1已保存评论 0

文章操作

快速查看文章及其快照的属性,并进行相关操作。

当前评论
0 条
当前快照
1 份
快照标识符
@mipjm2nk
此快照首次捕获于
2025/12/03 13:04
3 个月前
此快照最后确认于
2025/12/03 13:04
3 个月前
查看原文

A.最小生成树

题意:

nn 个点,mm 条边,每条边有两个边权 ai,bia_i,b_i,可以选一个定为该边边权。对于每个 0cm0\le c\le m,求恰好选 ccaia_imcm-cbib_i 时最大的最小生成树。
n9,m100n\le 9,m\le 100

Sol:

Kruskal。
2m2m 条边从小到大排序,设 dpi,j,fdp_{i,j,f} 表示已经考虑到第 ii 条边,已选 jjbib_i,并查集状态为 ff 时的最大值。
考虑怎么转移。
每一条边有两个分边,在较小的那条时确定选 ai/bia_i/b_i
如果 xi,yix_i,y_i 已在一个连通块内:
如果这条边是第一个分边,则 jj 可以加一,否则 jj 只能不加;
如果 xi,yix_i,y_i 不在一个连通块内:
如果这条边是第一个分边,则可以选择加入/不加入生成树,jj 加上对应的贡献,否则只能加入生成树。
用个 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.操作

题意:

给出 nn 个操作以及模数 pp
有一个初始为 11 的变量 xx,操作 11xvx\leftarrow v;操作 22xx×vx\leftarrow x\times v
可以以任意顺序操作,问 [0,p)[0,p) 中有多少个数无论如何都不能在最后得到。
n,p106n,p\le 10^6

Sol:

等价于在集合 00 中选一个数,在集合 11 中选若干个数(可不选),把它们乘起来。
用一个 pp 的原根 gg 表示所有数,则乘法转为加法,变成卷积形式。初始时令答案集合为 00 集合,依次卷上 11 集合中的每个数,可以用 bitset。
发现每个数只会加入 11 次,即更新次数 O(p)O(p)
于是想到分治+哈希。设我们正在加入 vv,当区间 [l,r][l,r][l+v,r+v][l+v,r+v] 的状态不一样时我们才递归进去。然后用树状数组维护哈希。
证明一下这样的次数是对的:
注意到 ii+vi\rightarrow i+v 会形成若干个环,而环上相邻位置状态为 0101 的数量是等于 1010 的数量的,因此总的递归到最底层的次数还是 O(p)O(p) 的。每次递归到最底层会走 log\log 层,然后每层树状数组求哈希 O(log)O(\log),所以总复杂度为 O(plog2p)O(p\log^2p)

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
给出一个排列 pp,生成一个 nn 个点的无向图,对于 i<ji<j,它们之间有边,当且仅当 pi>pjp_i>p_j,长度为 11
对于每个 iijdis(i,j)\sum\limits_j dis(i,j),如果不联通则 dis=0dis=0
n2×105n\le 2\times10^5

Sol:

首先,如果 maxj=1ipj=i\max\limits_{j=1}^{i}p_j=i,则称 ii 为关键点,相邻的两个关键点 [keyi1+1,keyi][key_{i-1}+1,key_i] 之间是一个极大连通块,证明考虑从这个区间的极大值处断开,然后归纳。
其次,将排列视为平面上的 nn 个点 (pi,i)(p_i,i),则 dis(i,j)kdis(i,j)\ge k 的点是平面左下角一个矩形或右上角一个矩形内,这个矩形的边界由上一次我们走到的最左、最下/最右、最上的点决定,可以归纳证明。
然后发现这两个矩形其实是独立的,因为我们总是用上次最左的点更新现在最下的点,用上次最下的点更新这次最左的点。
所以左下、右上角是等价的。下面只考虑左下角怎么做,右上角把整个平面旋转 180180 度即可。
设从 ii 出发,走了 kk 步后,能到达的最做的点为 xx,最下的点为 yy,则 disi,j>k+1j<xpj<pydis_{i,j}>k+1\Leftrightarrow j<x\land p_j<p_y
lsils_i 表示满足 jij\le ipjpip_j\ge p_i 的最小的 jjrsirs_i 表示满足 jij\ge ipjpip_j\le p_i 的、同时 pjp_j 最小的 jj,则 (x,y)(x,y) 下一步会走到 (lsy,rsx)(ls_y,rs_x)。然后 ii 的答案就是从 (i,i)(i,i) 一直跳,经过的所有点的左下方的点数之和。
不难发现可以由跳的过程形成由向根树构成的森林。有结论:所有点经过的 (x,y)(x,y) 总个数是 O(nn)O(n\sqrt{n}) 的,证明如下:
对于每个 xx,将所有 (x,y)(x,y)pyp_y 升序排序,得到 (x,cx,1),(x,cx,2)...(x,cx,m)(x,c_{x,1}),(x,c_{x,2})...(x,c_{x,m}),称 ini\le \sqrt{n}(x,cx,i)(x,c_{x,i}) 为好的点,其它的点为坏的点。
注意到对于所有点 (x,y)(x,y),都有 xy,pxpyx\le y,p_x\ge p_y。所以,如果在跳的过程中我们经过了某个坏的点 (x,cx,i),i>n(x,c_{x,i}),i>\sqrt{n},则下一步 yy 至少会走到 cx,1c_{x,1},即 pyp_y 至少减少 n\sqrt{n}。由于一次跳的过程中 pyp_y 不增,所以一次至多经过 n\sqrt{n} 个坏点,总个数 O(nn)O(n\sqrt{n})。而好点个数不超过 O(nn)O(n\sqrt{n}),所以树的点数为 O(nn)O(n\sqrt{n}) 的。
考虑怎么建树。显然可以暴力跳,然后用 set 之类的维护点集,但这样会有个 log\log
发现 prsxp_{rs_x}xx 的减小而减小。每个 xx 维护一个栈 stxst_x 表示待加入点集的点,点按 pyp_y 降序排序。从大到小扫 xx,将栈中的点加入点集,加入 (x,y)(x,y) 时再将点 (lsy,rsx)(ls_y,rs_x) 插入栈 stlsyst_{ls_y} 里面。由于我们从达到小扫 xx,所以 prsxp_{rs_x} 一定是最小的,和 stlsyst_{ls_y} 的栈顶比较一下是否相同即可。这样可以得到点集和所有点的父亲。
查询的话,从小到大扫 xx,每个点的权值等于其父亲的权值加上自己左下方的点数。查询左下方的点数时,由于有 O(nn)O(n\sqrt{n}) 次查询 O(n)O(n) 次插入所以用分块平衡一下复杂度。ii 的答案就是 (i,i)(i,i) 的权值。
一些细节:
1.最前面有说 [keyi1+1,keyi][key_{i-1}+1,key_{i}] 是一个极大联通快,因此经过一个关键点时要清空分块。
2.我们只算了 disi,j>k+1(k0)dis_{i,j}>k+1(k\ge 0) 的贡献,所以及的加上 disi,j>0dis_{i,j}>0 的贡献,即 keyi1<jkeyi,ansjansj+keyikeyi11\forall key_{i-1}<j\le key_i,ans_j\leftarrow ans_j+key_i-key_{i-1}-1

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 条评论,欢迎与作者交流。

正在加载评论...