2024.8.1 模拟赛13

模拟赛

最崩的一场,断断续续补题解。

“小孩召开法” 专场。

T1 Shiritori

很裸的一道博弈论,不用考虑 SG 函数这种高级东西,从最简单的定义出发。

把所有字符串收尾相连建边,会得到一张 DAG,然后就是经典有向图问题。

如果最后没有后继节点,那必赢,如果所有子节点都是必赢的状态,那必输,否则必赢。

注意状压记忆化。

code
#include<bits/stdc++.h>
using namespace std;
#define scan __builtin_scanf
#define print __builtin_printf
#define LL long long
const int N = 17;

int n,ans;
int f[1<<16][17],len[N];
char s[N][15];

bool dfs(int i,int h)
{
	if(~f[i][h]) return f[i][h];
	for(int j=1;j<=n;j++)
	{
		if((1<<j-1)&i) continue;
		if(s[j][1]==s[h][len[h]]) 
		{
			if(!dfs(i|(1<<j-1),j)) return f[i][h]=1;
		}
	}
	return f[i][h]=0;
}
int main()
{
	// freopen("in.in","r",stdin);
	// freopen("out.out","w",stdout);
	memset(f,-1,sizeof(f));
	scan("%d",&n);
	for(int i=1;i<=n;i++) scan("%s",(s[i]+1)),len[i]=strlen(s[i]+1);
	for(int i=1;i<=n;i++)
	{
		if(!dfs(1<<i-1,i)) return print("First\n"),0;
	}
	print("Second\n");
	return 0;
}

T2 Nauuo and Binary Tree

比较好想的是处理深度,花费 \(n-1\) 次询问。

然后按深度一层一层填树,所以填到第 \(i\) 层的时候,我们已知前 \(i-1\) 层的信息。

如果我们查询要填的这个点和一个已知点的距离,可以得到 lca 的深度 \(dep=dep_u+dep_v-2 \times dis\)

如何通过 lca 去找当前点的位置呢?为了将操作次数压在 \(log\),这里引入树剖的思想。

我们记录每条重链的链尾(深度最深的),然后查询链尾和当前点的距离得到 lca 的深度。

lca 一定就在这条重链上,所以暴跳下去。如果找到 lca 的父亲还不是当前节点的父亲,那就跳一次轻儿子,再重复上述过程。

因为重链只会有 \(log\) 条,所以每次最多查询 \(log\) 次。

code
#include<bits/stdc++.h>
using namespace std;
#define scan __builtin_scanf
#define print __builtin_printf
#define LL unsigned int

const int N = 3005;
int n,fa[N],son[N][2],dep[N],sz[N],ta[N];
vector<int> v[N];
inline void ask(int x,int y)
{
	cout<<"? "<<x<<" "<<y<<endl; 
}
inline void add(int f,int u)
{
	fa[u]=f;
	son[f][0]?son[f][1]=u:son[f][0]=u;
}
void dfs(int u)
{
	sz[u]=1; ta[u]=u;
	if(son[u][0])
	{
		dfs(son[u][0]);
		sz[u]+=sz[son[u][0]];
	}
	if(son[u][1])
	{
		dfs(son[u][1]);
		sz[u]+=sz[son[u][1]];
		if(sz[son[u][1]]>sz[son[u][0]]) swap(son[u][0],son[u][1]);
	}
	if(son[u][0]) ta[u]=ta[son[u][0]];
}
void work(int u)
{
	int v=1,dis;
	while(dep[v]!=dep[u]-1)
	{
		ask(u,ta[v]);
		scan("%d",&dis);
		dis=(dep[u]+dep[ta[v]]-dis)>>1;
		while(dep[v]<dis) v=son[v][0];
		if(dep[v]==dep[u]-1) break;
		v=son[v][1];
	}
	add(v,u);
}
int main()
{	
	// freopen("in.in","r",stdin);
	// freopen("out.out","w",stdout);
	scan("%d",&n);
	for(int i=2;i<=n;i++)
	{
		ask(1,i);
		scan("%d",&dep[i]);
		v[dep[i]].push_back(i);
	}
	for(int i=1;i<=n;i++)
	{
		dfs(1);
		for(int j:v[i]) work(j);
	}
	cout<<"!";
	for(int i=2;i<=n;i++) cout<<" "<<fa[i];
	cout<<endl;
	return 0;
}

T3 好吃的题目

猫树分治板子(学完整体二分可能理解更深)。

“猫树”长这个样子

猫树分治通常可以解决具有以下特征的问题:

  • 静态序列,多组查询。

  • 对于查询的区间我们可以从中间分成两部分然后合并。也就是区间之间可以结合。

假设查询区间为 \([l,r]\),我们已经知道 \([l,mid],[mid+1,r]\) 的答案。那么可以把左右分别看成两个物品,枚举容量的划分,就是 \(O(V)\) 的背包合并。

既然答案可以由两部分合成,那我们就整体二分,从 \(mid\) 向左右扩展计算答案,如果询问正好包含 \(mid\),那可以直接合并。否则就下放到左右区间递归求解。

注意背包数组每次都要初始化,表示起点为 \(mid/mid+1\),因为我们要知道从 \(mid/mid+1\) 开始到 \([l,r]\) 每个位置的最优解,所以注意继承答案 \(f_{i,j}=f_{i-1/i+1/j}\)

code
#include<bits/stdc++.h>
using namespace std;
#define mx(x,y) (x>y?x:y)
const int N = 4e4+5,M = 2e5+5;
int n,m;
int h[N],cnt;
int f[N][201],w[N],ans[M];
struct Q {int l,r,t,id;} q[M],q1[M],q2[M];
void work(int l,int r,int ql,int qr)
{
	if(ql>qr) return;
	if(l==r) return ;
	int mid=l+r>>1;
	for(int i=0;i<=200;i++) f[mid][i]=0;
	for(int i=mid+1;i<=r;i++)
	{
		for(int j=0;j<=200;j++) f[i][j]=f[i-1][j];
		for(int j=h[i];j<=200;j++) f[i][j]=max(f[i][j],f[i-1][j-h[i]]+w[i]);
	}
	for(int i=0;i<h[mid];i++) f[mid][i]=0; for(int i=h[mid];i<=200;i++) f[mid][i]=w[mid];
	for(int i=mid-1;i>=l;i--)
	{
		for(int j=0;j<=200;j++) f[i][j]=f[i+1][j];
		for(int j=h[i];j<=200;j++) f[i][j]=max(f[i][j],f[i+1][j-h[i]]+w[i]);
	}
	int c1=0,c2=0;
	for(int i=ql;i<=qr;i++)
	{
		if(q[i].r<=mid) q1[++c1]=q[i];
		else if(q[i].l>mid) q2[++c2]=q[i];
		else
		{
			int tmp=0;
			for(int j=0;j<=q[i].t;j++) tmp=mx(tmp,f[q[i].l][j]+f[q[i].r][q[i].t-j]);
			ans[q[i].id]=tmp;
		}
	}
	for(int i=ql;i<=ql+c1-1;i++) q[i]=q1[i-ql+1]; 
	for(int i=ql+c1;i<=ql+c1+c2-1;i++) q[i]=q2[i-c1-ql+1];
	work(l,mid,ql,ql+c1-1);work(mid+1,r,ql+c1,ql+c1+c2-1);
}
int main()
{
	// freopen("in.in","r",stdin);
	// freopen("out.out","w",stdout);
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++) scanf("%d",&h[i]);
	for(int i=1;i<=n;i++) scanf("%d",&w[i]);
	for(int i=1;i<=m;i++)
	{
		int l,r,t; scanf("%d%d%d",&l,&r,&t);
		if(l>r) swap(l,r);
		if(l==r) ans[i]=w[l]*(t>=h[l]);
		else q[++cnt]={l,r,t,i};
	}
	work(1,n,1,cnt);
	for(int i=1;i<=m;i++) printf("%d\n",ans[i]);
	return 0;
}

T4 Range Argmax

抽象,咕。。。

小孩召开法

有机会学多项式再回来做吧。

posted @ 2024-09-05 10:42  ppllxx_9G  阅读(9)  评论(0编辑  收藏  举报