Typesetting math: 100%

省选模拟测试8

 


考的河北 1313 年的省选题。

实际得分:15+100+20=13515+100+20=135

T1T1 博弈论的题,读懂题目就花了半个小时。

T2T2 之前做过的一道题,不说了。

T3T3 一道推柿子的题,不知道为什么链的暴力分挂掉了。

T1 Eden的博弈树#

题目描述#

洛谷

给你一棵博弈树,叶子节点的状态是任意的,定义最小黑方胜集合为选最少的叶子节点使他们的状态为黑方必胜态,且根节点的状态为黑方必胜。最小白色胜集合为选最少的叶子节点使他们的状态为白方必胜,且根节点为白方必胜。问你既属于最小黑方胜集合,又属于最小白方胜集合的叶子节点的编号最小值,个数以及编号的异或和。

数据范围:n2×105n2×105

solution#

树形 dpdp 加博弈论。

f[i][0/1]f[i][0/1] 表示 ii 号点为白方/黑方必胜态的最少需要叶子结点的个数。

分情况来讨论转移:

  • ii 号点为黑方操作,ii 号点为黑方必胜态只需要满足儿子节点中有一个为黑方必胜态,即 f[i][1] = min(f[i][1],f[to][1])
  • ii 号点为白方操作,则 ii 号点为黑方必胜态就需要满足儿子节点都为黑方必胜态,即 f[i][1] += f[to][1]
  • ii 号点为黑方操作,则 ii 号点为白方必胜态就需要满足儿子节点都为白方必胜态,即 f[i][0] += f[to][0]
  • ii 号点为白方操作,则 ii 号点为白方必胜态只需要满足儿子节点中有一个为白方必胜态,即 f[i][0] = min(f[i][0],f[to][0])

在求 f[i][0/1]f[i][0/1] 的同时,拿 vectorvector 存下当前这个点是由谁转移过来的。

找属于最小黑方胜集合中的叶子节点时,我们从根节点开始搜索,每次往当前节点的转移节点 (vectorvector 中的点)中搜索,所搜到的叶子节点就是属于最小黑方胜集合中的点。找最小白方胜集合中的点同理。

代码很好实现,就 44DFSDFS

 update: update: 其实不需要拿 vectorvector 存由谁转移过来的,只需要判断 f[i][0/1]f[i][0/1]f[to][0/1]f[to][0/1] 就可以找到转移节点。

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N = 2e5+10;
int n,v,tot,ans1,ans2,ans3;
int head[N],siz[N],dep[N],f[N][2],num[N];
struct node
{
	int to,net;
}e[N<<1];
inline int read()
{
	int s = 0,w = 1; char ch = getchar();
	while(ch < '0' || ch > '9'){if(ch == '-') w = -1; ch = getchar();}
	while(ch >= '0' && ch <= '9'){s = s * 10 + ch - '0'; ch = getchar();}
	return s * w;
}
void add(int x,int y)
{
	e[++tot].to = y;
	e[tot].net = head[x];
	head[x] = tot;
}
void dfs1(int x,int fa,int type)
{
	dep[x] = dep[fa]+1; siz[x] = 1; 
	if((dep[x] & 1) == type) f[x][type] = n+1;
	for(int i = head[x]; i; i = e[i].net)
	{
		int to = e[i].to;
		if(to == fa) continue;
		dfs1(to,x,type);
		siz[x] += siz[to];
		if((dep[x] & 1) == type) f[x][type] = min(f[x][type],f[to][type]);
		else f[x][type] += f[to][type];
	}
	if(siz[x] == 1) f[x][type] = 1;
}
void dfs2(int x,int fa,int type)
{
	if(siz[x] == 1) num[x]++;
	for(int i = head[x]; i; i = e[i].net)
	{
		int to = e[i].to;
		if(to == fa) continue;
		if((dep[x] & 1) != type) dfs2(to,x,type);
		else if(f[x][type] == f[to][type]) dfs2(to,x,type); 
	}
}
int main()
{
//	freopen("a.in","r",stdin);
//	freopen("a.out","w",stdout);
	n = read(); ans1 = n+1;
	for(int i = 2; i <= n; i++)
	{
		v = read();
		add(i,v); add(v,i);
	}
	dfs1(1,0,1); dfs1(1,0,0);
	dfs2(1,0,1); dfs2(1,0,0);
	for(int i = 1; i <= n; i++)
	{
		if(num[i] == 2) 
		{
			ans1 = min(ans1,i);
			ans2++;
			ans3 ^= i;
		}
	}
	printf("%d %d %d\n",ans1,ans2,ans3);
	fclose(stdin); fclose(stdout);
	return 0;
}

T2 ALO#

题意描述#

洛谷

给你一个序列,定义一个区间的价值为区间的次大值 kk 和区间中所有元素异或的最大值。

问你价值最大的区间的价值为多少。

数据范围:n50000,ai109n50000,ai109

solution#

我们可以求出每个元素作为次大值的区间,然后用可持久化 tiretire 树就可以求出当前元素和这一段区间的元素的异或最大值。

问题就转化为怎么求每个元素作为次大值的区间。

head[x]head[x] 为左边第一个比 a[x]a[x] 大的元素,net[x]net[x] 为右边第一个比 a[x]a[x] 大的元素。

那么 a[x]a[x] 可以作为次大值的区间为:head[head[x]]+1net[x]1head[head[x]]+1net[x]1head[x]+1,net[net[x]]1head[x]+1,net[net[x]]1

head[x],net[x]head[x],net[x] 可以拿链表维护出来。

然后这道题就做完了,复杂度 O(nlog2n)O(nlog2n)

代码实现也比较简单,主要是一些边界问题要注意一下。

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N = 1e5+10;
int n,ans,tot;
int a[N],head[N],net[N],rt[N],siz[10000010],tr[10000010][2];
struct node
{
	int pos,w;
}e[N];
inline int read()
{
	int s = 0, w = 1; char ch = getchar();
	while(ch < '0' || ch > '9'){if(ch == '-') w = -1; ch = getchar();}
	while(ch >= '0' && ch <= '9'){s = s * 10 + ch - '0'; ch = getchar();}
	return s * w;
}
bool comp(node a,node b)
{
	return a.w < b.w;
}
void insert(int p,int q,int x)
{
	for(int i = 30; i >= 0; i--)
	{
		int c = (x>>i)&1;
		siz[p] = siz[q] + 1;
		tr[p][!c] = tr[q][!c];
		tr[p][c] = ++tot;
		p = tr[p][c];
		q = tr[q][c];
	}
	siz[p] = siz[q] + 1;
}
int query(int p,int q,int x)
{
	int res = 0;
	for(int i = 30; i >= 0; i--)
	{
		int c = (x>>i)&1;
		if(siz[tr[q][!c]]-siz[tr[p][!c]] > 0)
		{
			res += (1<<i);
			p = tr[p][!c];
			q = tr[q][!c];
		}
		else
		{
			p = tr[p][c];
			q = tr[q][c];
		}
	}
	return res;
}
int main()
{
	n = read();
	rt[0] = ++tot;
	insert(rt[0],rt[0],0);
	for(int i = 1; i <= n; i++) 
	{
		a[i] = read();
		e[i].w = a[i];
		e[i].pos = i;
		rt[i] = ++tot;
		insert(rt[i],rt[i-1],a[i]);
	}
	for(int i = 1; i <= n+1; i++) head[i] = i-1, net[i] = i+1;
	net[0]=1;
	sort(e+1,e+n+1,comp);
	for(int i = 1; i <= n; i++)
	{
		int x = e[i].pos;
		if(head[x] != 0) ans = max(ans,query(rt[head[head[x]]],rt[net[x]-1],e[i].w));
		if(net[x] != n+1) ans = max(ans,query(rt[head[x]],rt[min(n,net[net[x]]-1)],e[i].w));
		net[head[x]] = net[x];
		head[net[x]] = head[x];
		head[x] = net[x] = -1;
	}
	printf("%d\n",ans);
	return 0;
}

T3 SAO#

题意描述#

洛谷

Welcome to SAO ( Strange and Abnormal Online)。这是一个 VR MMORPG, 含有 nn 个关卡。但是,挑战不同关卡的顺序是一个很大的问题。

n1n1 个对于挑战关卡的限制,诸如第 ii 个关卡必须在第 jj 个关卡前挑战, 或者完成了第 kk 个关卡才能挑战第 ll 个关卡。并且,如果不考虑限制的方向性, 那么在这 n1n1 个限制的情况下,任何两个关卡都存在某种程度的关联性。即, 我们不能把所有关卡分成两个非空且不相交的子集,使得这两个子集之间没有任何限制。

对于每个数据,输出一行一个整数,为攻克关卡的顺序方案个数,mod1,000,000,007mod1,000,000,007 输出。

简化题意:给你 n1n1 个限制,限制包括 ii 要排在 jj 前面或者 ii 要排在 jj 之后。问你有多少种排列满足这 n1n1 个限制。

数据范围:T5,n1000T5,n1000

solution#

树形 dpdp

我们可以把这 n1n1 个限制看成边,这样就构成了一棵树。

我们设 f[i][j]f[i][j] 表示在 iiii 的子树构成的排列中,ii 排第 jj 个的方案数。

转移的时候,每次把 toto 所在的子树合并进来,即把 f[to][j]f[to][j] 算到 f[i][k]f[i][k] 里面,分情况讨论一下。

  • xx 要在 toto 之后。

枚举 totototo 的子树中排名,则有:

f[x][i+j]=jk=1f[x][i]×f[to][k]×(i+j1i1)×(siz[x]+siz[to]ijsiz[x]i)f[x][i+j]=jk=1f[x][i]×f[to][k]×(i+j1i1)×(siz[x]+siz[to]ijsiz[x]i)

简单来说就是,如果合并完 toto 这棵子树后 xx 的排名为 i+ji+j, 那么就意味着要从 toto 子树中选 jj 个点排在 xx 的前面。因为 xx 要在 toto 之后,所以 toto 必然在选出来的 jj 个点当中,即 totototo 的子树中的排名范围为 1j1j

合并后 xx 的前面有 i+j1i+j1 个位置,我们要把之前排在 xx 前面的 i1i1 个节点按排名顺序放到这 i+j1i+j1 个位置中,方案数即为 (i+j1i1)(i+j1i1) , 同理 xx 的后面有 siz[x]+siz[to]ijsiz[x]+siz[to]ij 个位置,排在 xx 之后的 siz[x]isiz[x]i 的节点按排名顺序放入这些位置的方案数为 (siz[x]+siz[to]ijsiz[x]i)(siz[x]+siz[to]ijsiz[x]i)

  • xxtoto 之前。

还是枚举 totototo 子树中的排名。

f[x][i+j]=siz[to]k=j+1f[x][i]×f[to][k]×(i+j1i1)×(siz[x]+siz[to]ijsiz[x]i)f[x][i+j]=siz[to]k=j+1f[x][i]×f[to][k]×(i+j1i1)×(siz[x]+siz[to]ijsiz[x]i)

转移柿子和上面的差不多,只不过是 kk 的枚举范围变了,因为 toto 要排在 xx 之后,所以 totototo 的子树中的排名就要大于选出来的 jj 个点,即 totototo 的子树中的排名范围为 j+1siz[to]j+1siz[to]

这么直接转移的复杂度是 O(n3)O(n3) 的。

但我们发现当你枚举 kk 的时候,后面乘的那一坨组合数是一样的。

考虑维护 pre[x][i]=ij=0f[x][j]pre[x][i]=ij=0f[x][j] ,suf[x][i]=siz[x]+1j=if[x][j]suf[x][i]=siz[x]+1j=if[x][j]

转移的时候用 pre[x][i]pre[x][i]suf[x][i]suf[x][i] 就可以省掉枚举 kk 的时间。

复杂度 O(n2)O(n2)

Code

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
#define int long long
const int p = 1e9+7;
const int N = 2010;
int T,n,u,v,tot,ans;
int head[N],siz[N],f[N][N],g[N],c[N][N],sum1[N][N],sum2[N][N];
struct node
{
    int to,net,id;
}e[N<<1];
inline int read()
{
    int s = 0,w = 1; char ch = getchar();
    while(ch < '0' || ch > '9'){if(ch == '-') w = -1; ch = getchar();}
    while(ch >= '0' && ch <= '9'){s = s * 10 + ch - '0'; ch = getchar();}
    return s * w;
}
void add(int x,int y,int id)
{
    e[++tot].to = y;
    e[tot].id = id;
    e[tot].net = head[x];
    head[x] = tot;
}
void dfs(int x,int fa)
{
    siz[x] = 1; f[x][1] = 1;
    for(int i = head[x]; i; i = e[i].net)
    {
        int to = e[i].to;
        if(to == fa) continue;
        dfs(to,x);
        memset(g,0,sizeof(g));//新开一个g数组避免重复更新
        if(e[i].id == 1)
        {
            for(int j = 0; j <= siz[x]; j++)
            {
                for(int k = 0; k <= siz[to]; k++)
                {
                    g[j+k] = (g[j+k] + f[x][j] * c[j+k-1][j-1] % p * c[siz[x]+siz[to]-j-k][siz[x]-j] % p * sum1[to][k] % p) % p;
                }
            }
        }
        else
        {
            for(int j = 0; j <= siz[x]; j++)
            {
                for(int k = 0; k <= siz[to]; k++)
                {
                    g[j+k] = (g[j+k] + f[x][j] * c[j+k-1][j-1] % p * c[siz[x]+siz[to]-j-k][siz[x]-j] % p * sum2[to][k+1] % p) % p;
                }
            }
        }
        siz[x] += siz[to];
        for(int j = 0; j <= siz[x]; j++) f[x][j] = g[j];
    }
    sum1[x][0] = 0; sum2[x][n+1] = 0;
    for(int i = 1; i <= siz[x]; i++) sum1[x][i] = (sum1[x][i-1] + f[x][i]) % p;
    for(int i = siz[x]; i >= 1; i--) sum2[x][i] = (sum2[x][i+1] + f[x][i]) % p;
}
void qingkong()
{
    ans = tot = 0;
    memset(head,0,sizeof(head));
    memset(siz,0,sizeof(siz));
    memset(sum1,0,sizeof(sum1));
    memset(sum2,0,sizeof(sum2));
    memset(f,0,sizeof(f));
}
signed main()
{
    T = read();
    c[0][0] = 1;
    for(int i = 1; i <= 2000; i++)
    {
        c[i][0] = 1;
        for(int j = 1; j <= i; j++)
        {
            c[i][j] = (c[i-1][j-1] + c[i-1][j]) % p;
        }
    }
    while(T--)
    {
        n = read(); qingkong(); char ch;
        for(int i = 1; i <= n-1; i++)
        {
            u = read() + 1; cin>>ch; v = read() + 1;
            if(ch == '<') add(u,v,0), add(v,u,1);
            else add(v,u,0), add(u,v,1);
        }
        dfs(1,0);
        for(int i = 1; i <= n; i++) ans = (ans + f[1][i]) % p;
        printf("%lld\n",ans);
    }
    return 0;
}
posted @   genshy  阅读(54)  评论(0编辑  收藏  举报
编辑推荐:
· 智能桌面机器人:用.NET IoT库控制舵机并多方法播放表情
· Linux glibc自带哈希表的用例及性能测试
· 深入理解 Mybatis 分库分表执行原理
· 如何打造一个高并发系统?
· .NET Core GC压缩(compact_phase)底层原理浅谈
阅读排行:
· DeepSeek火爆全网,官网宕机?本地部署一个随便玩「LLM探索」
· 开发者新选择:用DeepSeek实现Cursor级智能编程的免费方案
· 【译】.NET 升级助手现在支持升级到集中式包管理
· 独立开发经验谈:如何通过 Docker 让潜在客户快速体验你的系统
· Tinyfox 发生重大改版
点击右上角即可分享
微信分享提示
主题色彩