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
抽象,咕。。。
小孩召开法
有机会学多项式再回来做吧。