0220 模拟赛(尧)
「CF1658E」Gojou and Matrix Game
题意
\(~~~~\) 大小 \(n\times n\) 的棋盘,里面有 \(n^2\) 以内每个整数恰好一个。先手第一次将棋子放在某个各自中,此后两人轮流将棋子移动到曼哈顿距离到当前位置 \(>k\) 的地方,过程中自己获得移动/放置到的位置的权值。最终判断每个位置先手能否获得更大权值。
\(~~~~\) \(1\leq n\leq 2000,1\leq k\leq n-2\).
题解
\(~~~~\) 有shaber组题人。
\(~~~~\) 良心组题人给的样例告诉我们:最大值处放置的棋子是先手必胜的:因为后手必然移动到权值更小的地方,那么先手此时再移回去即可。
\(~~~~\) 因此,当先手放置的位置距离最大值大于 \(k\) 时后手可以把棋子移动到最大值,所以这些位置先手必败。
\(~~~~\) 现在来考虑这个“圆”(曼哈顿距离下)内部的位置,我们发现每个人都一定试图把棋子移到更大的地方,那么我们就可以把权值从大到小考虑,只要可以移动到必胜点那就是必败点,否则必胜点。
\(~~~~\) 最后就是怎么维护有没有这样的点的问题,曼哈顿距离加法使得两边相关性太强了,转成切比雪夫距离,我们只要求某一维到该点对应维的差是否在 \(k\) 之外即可,那维护每一维上最小/最大共四个值即可。
代码
查看代码
#include <bits/stdc++.h>
#define PII pair<int,int>
#define mp(a,b) make_pair(a,b)
using namespace std;
template<typename T>void read(T &x)
{
T f=1;x=0;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9') {x=x*10+ch-'0';ch=getchar();}
x*=f;
}
struct node{
int val,x,y;
node(){}
node(int Val,int X,int Y){val=Val,x=X,y=Y;}
}P[4000005];
int arr[2005][2005];
char Map[2005][2005];
inline int Abs(int x){return x>0?x:-x;}
bool cmp(node a,node b){return a.val>b.val;}
int main() {
// freopen("tide.in","r",stdin);
// freopen("tide.out","w",stdout);
int n,k,posx=0,posy=0;read(n);read(k);
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
read(arr[i][j]); Map[i][j]='Y';
if(arr[i][j]==n*n) posx=i,posy=j;
}
}
int cnt=0;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++) if(Abs(i-posx)+Abs(j-posy)<=k) P[++cnt]=node(arr[i][j],i,j);
sort(P+1,P+1+cnt,cmp);
int MaxnX=-1e9,MaxnY=-1e9,MinnX=1e9,MinnY=1e9;
Map[P[1].x][P[1].y]='M'; MaxnX=MinnX=P[1].x+P[1].y; MaxnY=MinnY=P[1].x-P[1].y;
for(int i=2;i<=cnt;i++)
{
int X=P[i].x,Y=P[i].y;
if(max(max(Abs(X+Y-MaxnX),Abs(X+Y-MinnX)),max(Abs(X-Y-MinnY),Abs(X-Y-MaxnY)))>k) continue;
Map[X][Y]='M';
MaxnX=max(MaxnX,X+Y); MinnX=min(MinnX,X+Y);
MaxnY=max(MaxnY,X-Y); MinnY=min(MinnY,X-Y);
}
for(int i=1;i<=n;i++) puts(Map[i]+1);
return 0;
}
/*
瑶草一何碧,春入武陵溪。溪上桃花无数,花上有黄鹂。我欲穿花寻路,直入白云深处,浩气展虹霓。只恐花深里,红露湿人衣。
坐玉石,欹玉枕。拂金徽。谪仙何处,无人伴我白螺杯。我为灵芝仙草,不为朱唇丹脸,长啸亦何为。醉舞下山去,明月逐人归。
首先注意到最大值肯定是先手必胜,原因写在样例解释
那么最大值的半径为k的曼哈顿距离下的圆外的必败,因为后手直接选到最大值
类推一下,后手可以胜当且仅当其能转移到一个比先手所选点大的必胜点
把圆内的数从大到小考虑,然后看当前加入必胜的数有无曼哈顿距离跟自己大于k者,有则必败,无则必胜。
查询一个集合内是否存在一个元素与自己曼哈顿距离大于一个定值
转化成切比雪夫距离大于一个定值,那么就是查最小/最大 x,最小/最大 y 和自己的差
随便维护维护大概就好了
*/
「GYM104128E」Color the Tree
题意
\(~~~~\) 一个大小为 \(n\) 的 \(1\) 为根的树,允许在任何点 \(u\) 处将 \(u\) 子树内距离其恰好为 \(k\) 的点以 \(a_k\) 的代价染黑。求将树上所有点染黑的最小代价。
\(~~~~\) \(1\leq n\leq 10^5\).
题解
\(~~~~\) 考场上所有人写长剖,只有组题人写题解做法,组题人成异类了!显然长剖刻骨铭心了
\(~~~~\) 很显然想到记录 \(dp_{u,i}\) 表示 \(u\) 结点子树内距离 \(u\) 为 \(i\) 的所有点染黑的最小代价,答案最后为 \(\sum_{i=0}^{n-1} dp_{1,i}\) 深度相关信息,考虑长剖。把每个点上的深度对应合并(记得位移),然后再与直接做对应深度的代价取 \(\min\) .
\(~~~~\) 出现一个问题:最后在每个点再进行 \(\mathcal{O(dep)}\) 的合并你会变成 \(\mathcal{O(n^2)}\) ,所以记录一个延迟标记为其上次更新时的 dep,那么下次再用这个值的时候就把这个区间内的单独取的代价一起取上,ST表维护即可。
\(~~~~\) 用 vector
实现的,所以要倒序记录第二维方便位移。建议还是用指针,真的直观很多/ll
代码
查看代码
#include <bits/stdc++.h>
#define ll long long
#define PII pair<ll,int>
#define mp(a,b) make_pair(a,b)
using namespace std;
template<typename T>void read(T &x)
{
T sig=1;x=0;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')sig=-1;ch=getchar();}
while(ch>='0'&&ch<='9') {x=x*10+ch-'0';ch=getchar();}
x*=sig;
}
int n,Cost[300005],dep[300005],MaxDep[300005],Det[300005],Son[300005];
vector <int> G[300005];
vector <PII> V[300005];
int lg[300005],f[300005][20];
void Init(int N)
{
lg[0]=-1;
for(int i=0;i<N;i++) f[i][0]=Cost[i];
for(int i=1;i<=N;i++) lg[i]=lg[i>>1]+1;
for(int j=1;j<=lg[N];j++)
for(int i=0;i+(1<<j)-1<N;i++)
f[i][j]=min(f[i][j-1],f[i+(1<<(j-1))][j-1]);
}
ll Query(int l,int r)
{
if(l>r) return 1e9;
int k=lg[r-l+1];
return min(f[l][k],f[r-(1<<k)+1][k]);
}
void dfs(int u,int fa)
{
dep[u]=dep[fa]+1; MaxDep[u]=dep[u];
for(int i=0;i<(int)G[u].size();i++)
{
int v=G[u][i];
if(v==fa) continue;
dfs(v,u);
MaxDep[u]=max(MaxDep[v],MaxDep[u]);
if(MaxDep[u]==MaxDep[v]) Son[u]=v;
}
}
inline void Clear(vector<PII>&A){vector<PII>Bin;swap(Bin,A);}
inline void Merge(int u,int v)
{
// cerr<<u<<" "<<v<<endl;
// for(ll i=0;i<V[u].size();i++) cerr<<V[u][i].first<<" ";cerr<<endl;
// for(ll i=0;i<V[v].size();i++) cerr<<V[v][i].first<<" ";cerr<<endl;
int Sizu=V[u].size(),Sizv=V[v].size();
for(int i=0;i<Sizv;i++)
{
int Dep=Sizv-i+dep[v]-1;
int Pos=Sizu+dep[u]-1-Dep;
V[u][Pos].first=min(V[u][Pos].first,Query(Dep-V[u][Pos].second,Dep-dep[u]))+min(V[v][i].first,Query(Dep-V[v][i].second,Dep-dep[v]));
V[u][Pos].second=dep[u];
}
// for(ll i=0;i<V[u].size();i++) cerr<<V[u][i].first<<" ";cerr<<endl<<"==="<<endl;
}
void dfs2(int u,int fa)
{
for(int i=0;i<(int)G[u].size();i++)
{
int v=G[u][i];
if(v==fa) continue;
dfs2(v,u);
}
if(!Son[u]) V[u].push_back(mp(Cost[0],dep[u]));
else
{
swap(V[u],V[Son[u]]); Clear(V[Son[u]]);
V[u].push_back(mp(Cost[0],dep[u]));
for(int i=0;i<(int)G[u].size();i++)
{
int v=G[u][i];
if(v==fa||v==Son[u]) continue;
Merge(u,v);Clear(V[v]);
}
}
}
int main() {
// freopen("dream.in","r",stdin);
// freopen("dream.out","w",stdout);
int T;read(T);
while(T--)
{
read(n); for(int i=1;i<=n;i++) G[i].clear();
for(int i=0;i<n;i++) read(Cost[i]);
Init(n);
for(int i=1,u,v;i<n;i++)
{
read(u);read(v);
G[u].push_back(v);
G[v].push_back(u);
}
dfs(1,0);
dfs2(1,0);
ll Ans=0;
for(int i=0;i<V[1].size();i++)
{
PII Tmp=V[1][i];
int Dep=V[1].size()-i;
Ans+=min(Tmp.first,Query(Dep-Tmp.second,Dep-1));
}
Clear(V[1]);
printf("%lld\n",Ans);
}
return 0;
}
/*
瑶草一何碧,春入武陵溪。溪上桃花无数,花上有黄鹂。我欲穿花寻路,直入白云深处,浩气展虹霓。只恐花深里,红露湿人衣。
坐玉石,欹玉枕。拂金徽。谪仙何处,无人伴我白螺杯。我为灵芝仙草,不为朱唇丹脸,长啸亦何为。醉舞下山去,明月逐人归。
在每个点处记录子树内k级儿子全部被覆盖的代价
在LCA处合并,显然是一个长剖
怎么又是长剖二连击?/yiw
位移怎么处理?
把每个倒着存好了
*/
「ARC112E」Cigar Box
题意
\(~~~~\) 长为 \(n\) 初始 \(a_i=i\) 的序列,一个操作定义为:将排列中某个数移动到第一个或最后一个。给出 \(\{b_i\}\) 求有多少操作 \(m\) 次,使得最终得到 \(a_i=b_i\) 的操作方案。
\(~~~~\) \(1\leq n,m\leq 3000\).
题解
\(~~~~\) 非常蚌啊,组题人放部分分不从指数爆搜开始。
\(~~~~\) 注意到对于某一个数的操作只有最后一次是有效的,那么我们关心的只有多少个有效操作。
\(~~~~\) 考虑 \(dp_{i,l,r}\) 表示共进行 \(i\) 次操作,其中操作了 \(l\) 次左,\(r\) 次右。那么此时中间 \([l+1,n-r]\) 内的数都应该是相对位置不变的,也即单调递增。
\(~~~~\) 转移方面:\(dp_{i,l,r} \leftarrow 2\times dp_{i+1,l,r}\times (l+r)\),表示进行了一次无效操作,其应排在这 \(l+r\) 个操作之前,并且往左或者往右随意。否则就是有效操作:\(dp_{i,l,r} \leftarrow dp_{i+1,l+1,r},dp_{i,l,r} \leftarrow dp_{i+1,l,r-1}\).
\(~~~~\) 状态太多了怎么办!我们发现所有转移中我们只关心 \(l+r\) ,所以把它们绑定在一起转移,那么新的 \(dp'_{i,l+r}\times \binom{l+r}{l}=dp_{i,l,r}\),最后我们枚举 \(l,r\) 放回来即可。
代码
查看代码
#include <bits/stdc++.h>
#define PII pair<int,int>
#define mp(a,b) make_pair(a,b)
using namespace std;
template<typename T>void read(T &x)
{
T f=1;x=0;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9') {x=x*10+ch-'0';ch=getchar();}
x*=f;
}
int Fac[3005],Inv[3005],arr[3005],dp[3005][3005];
const int MOD=998244353;
inline int Add(int a,int b){return (a+b)%MOD;}
inline int Dec(int a,int b){return (a-b+MOD)%MOD;}
inline int Mul(int a,int b){return 1ll*a*b%MOD;}
inline int qpow(int a,int b)
{
int ret=1;
while(b)
{
if(b&1) ret=Mul(ret,a);
b>>=1;a=Mul(a,a);
}
return ret;
}
inline int C(int n,int r){return Mul(Fac[n],Mul(Inv[r],Inv[n-r]));}
int main() {
int n,m;read(n);read(m); Fac[0]=1;
for(int i=1;i<=n;i++) read(arr[i]),Fac[i]=Mul(Fac[i-1],i);
Inv[n]=qpow(Fac[n],MOD-2);
for(int i=n-1;i>=0;i--) Inv[i]=Mul(Inv[i+1],i+1);
dp[0][0]=1;
for(int i=0;i<m;i++)
{
for(int j=0;j<=n;j++)
{
dp[i+1][j]=Add(dp[i+1][j],Mul(2,Mul(dp[i][j],j)));
dp[i+1][j+1]=Add(dp[i+1][j+1],dp[i][j]);
}
}
int Ans=0;
for(int l=0;l<=n;l++)
{
for(int r=n-l;r>=0;r--)
{
if(n-l-r>=2&&arr[n-r-1]>arr[n-r]) break;//中间部分递增
else Ans=Add(Ans,Mul(C(l+r,l),dp[m][l+r]));
}
}
printf("%d",Ans);
return 0;
}
/*
瑶草一何碧,春入武陵溪。溪上桃花无数,花上有黄鹂。我欲穿花寻路,直入白云深处,浩气展虹霓。只恐花深里,红露湿人衣。
坐玉石,欹玉枕。拂金徽。谪仙何处,无人伴我白螺杯。我为灵芝仙草,不为朱唇丹脸,长啸亦何为。醉舞下山去,明月逐人归。
*/
「GYM104128L」 Proposition Composition
不会。