一次比一次烂了,鉴定为不写模拟赛记录导致的。Rank
A. White and Black
被自己误导了,导致菊花和链的部分分没拿到。
经验++
对于每个点的父节点若有 $ 1 \le f_i \lt i $,则该图构成的菊花图根可能为 \(1\) 或 \(2\),链则不确定首尾。
Subtask 越来越不好判了www
思路
首先比较易得的是,不会出现无解的情况:因为任意一个节点我们都可以通过先翻转自身再翻转所有子节点的方式将其单独翻转。
其次,按暴力的方法找到思路:对于一个点集 \(S\) 内的点 \(m_i\),若他的父节点及以上已经进行了奇数次翻转操作,那么就不需要在该点翻转;若点集 \(S\) 之外的点的父节点及以上进行了奇数次翻转操作,此时该点处于翻转状态,我们需要在这里进行一次翻转操作。设 \(P=\sum_{i=1}^Q M_i\),则暴力的时间复杂度为 \(O(nP)\),显然是会 TLE 的。
考虑到要求反转的点的数量很小,我们尝试着枚举这些点求解。
读入时标记这些点,枚举时判断其父节点是否被标记,若不是意味着我们需要翻转该点,若是则说明他已经被翻转过。我们若仅翻转枚举的该点,那么对答案的贡献应为其子节点的数量之和加上 \(1\)。
简单画个图模拟一下,发现这样做会产生很多次无用操作。我们在翻转一个被标记的节点时,默认将其子节点再次翻回来,也就是上述的操作;如果一个点的父节点被标记,那么此时该点已经被翻转了两次,我们不用再次翻转该节点,且是要在操作其父节点时不将其二次翻转,这样才能得到最优操作次数。因此在遇到父节点被标记的节点时,我们要将答案减去 \(1\)。
细节处理
这道题搬到我们题库上只给了 \(1s\) 的时限,而标记用的数组每次只用了很少的一部分,所以操作完成后手动清零比使用 memset
要快很多。
数据的点集中会有父节点后被标记的情况,所以不能边读边做,要把点集先存下来。
Code:
#include<bits/stdc++.h>
const int Ratio=0;
const int N=2e5+5;
int n,m,tot,ans;
int fx[N],son[N];
int tag[N],s[N];
namespace Wisadel
{
short main()
{
scanf("%d%d",&n,&m);
for(int i=2;i<=n;i++)
scanf("%d",&fx[i]),son[fx[i]]++;
for(int i=1;i<=m;i++)
{
scanf("%d",&tot);ans=0;
for(int j=1;j<=tot;j++)
scanf("%d",&s[j]),tag[s[j]]=1;
for(int j=1;j<=tot;j++)
{
if(tag[fx[s[j]]]) ans--;
else ans++;
ans+=son[s[j]];
}
printf("%d\n",ans);
for(int j=1;j<=tot;j++) tag[s[j]]=0;
}
return Ratio;
}
}
int main(){return Wisadel::main();}
B. White and White
原题C3 放前两个没什么意义。
赛时想到了用和模上 \(p\) 的错解,但由于大样例没过没交,痛失硬控学长的机会。
思路
赛时只想出了 \(O(n^2k)\) 的超级暴力做法,所以从它开始优化吧。
首先,按定义 \(f_{i,j}\) 为到 \(i\) 位分成 \(j\) 段的模 \(p\) 意义下的最小和,则显然有 $f_{i,j}\equiv sum_i\pmod p $。
下面证明直接搬了。
Code:
#include<bits/stdc++.h>
#define fo(x,y,z) for(register int (x)=(y);(x)<=(z);(x)++)
#define fu(x,y,z) for(register int (x)=(y);(x)>=(z);(x)--)
using namespace std;
typedef long long ll;
#define lx ll
inline lx qr()
{
char ch=getchar();lx x=0,f=1;
for(;ch<'0'||ch>'9';ch=getchar())if(ch=='-')f=-1;
for(;ch>='0'&&ch<='9';ch=getchar())x=(x<<3)+(x<<1)+(ch^48);
return x*f;
}
#undef lx
#define qr qr()
const int Ratio=0;
const int N=5e5+1;
int n,k,p;
int a[N],sum[N],f[N][101],op[2][101];
namespace Wisadel
{
short main()
{
n=qr,k=qr,p=qr;
fo(i,1,n)
a[i]=qr%p,sum[i]=(sum[i-1]+a[i])%p;
memset(f,0x3f,sizeof f);
f[0][0]=0;
fo(i,1,n)
{
int fla=(i&1)^1;
fo(j,1,k)
f[i][j]=min(f[i][j],f[op[fla][j-1]][j-1]+((sum[i]-sum[op[fla][j-1]])%p+p)%p);
fo(j,0,k)
{
op[i&1][j]=op[fla][j];
if(f[i][j]<f[op[i&1][j]][j]) op[i&1][j]=i;
}
}
printf("%d\n",f[n][k]);
return Ratio;
}
}
int main(){return Wisadel::main();}
C. Black and Black
很神的构造题,赛时想的跟题解差不多,但没时间也没自信考场打出这么复杂的解法。
大体思路是对于每个确定的序列,都能使前缀和后缀同时加或减一个数,加减取决于前缀后缀的正负。我们可以通过调整来实现积的和为零,若无法实现即为无解。
Code:
#include<bits/stdc++.h>
#define fo(x,y,z) for(register int (x)=(y);(x)<=(z);(x)++)
#define fu(x,y,z) for(register int (x)=(y);(x)>=(z);(x)--)
typedef long long ll;
inline int qr()
{
char ch=getchar();int x=0,f=1;
for(;ch<'0'||ch>'9';ch=getchar())if(ch=='-')f=-1;
for(;ch>='0'&&ch<='9';ch=getchar())x=(x<<3)+(x<<1)+(ch^48);
return x*f;
}
#define qr qr()
const int Ratio=0;
const int N=2e5+1;
int n,k,p;
int a[N];
int pf,pz,sf,sz;
ll ans[N],sum;
namespace Wisadel
{
void Wgetqianhouzhui()
{
int cnt=0;
fo(i,1,n)
{
cnt+=a[i];// 记录前缀
if(!pf&&cnt<0) pf=i;
// 前缀和为负 记录
if(!pz&&cnt>0) pz=i;
// 为正同理
if(pz&&pf) break;
}
cnt=0;
fu(i,n,1)
{
cnt+=a[i];// 记录后缀
if(!sf&&cnt<0) sf=i;
if(!sz&&cnt>0) sz=i;
if(sz&&sf) break;
}
}
short main()
{
n=qr;
fo(i,1,n)
{
a[i]=qr;
ans[i]=i,sum+=a[i]*ans[i];
// 赋初值
}
Wgetqianhouzhui();
int pre=0,suf=0;
if(sum<0) sum=-sum,pre=pf,suf=sz;
else if(sum>0) pre=pz,suf=sf;
// 首先 前缀只能统一减小,后缀只能统一增大
// 关键 如果当前和为负,我们需要往大了调整
// 方案为负的前缀统一缩小 或 正的后缀统一增大
// 和为正,就要调小
// 方案为正的前缀缩小 或 负的后缀增大
if(sum)
{
if(pre) fo(i,1,pre) ans[i]-=sum;
else if(suf) fo(i,1,suf) ans[i]+=sum;
else
{
printf("No\n");
return Ratio;
}
}
printf("Yes\n");
fo(i,1,n) printf("%lld ",ans[i]);
return Ratio;
}
}
int main(){return Wisadel::main();}
D. Black and White
逆天题,改完再写。
完结撒花~
标签:ch,集训,int,sum,CSP,ans,节点,模拟,翻转 From: https://www.cnblogs.com/Ratio-Yinyue1007/p/18314824