CFR810 游记
CFR810 游记
人生中第一场熬夜打的 CF ,打前十分激动(大雾),然后就一直在玩 MC 。
Kewth 本来课件都没做打算做课件,结果还是禁不住 CF 的诱惑——课件可以明天上午再做嘛!
开 A ,为啥这么多字,然后勉强读完了感觉不是那么容易,然后翻样例,然后就知道怎么做了(大雾)
其实并不一定知道咋做,因为感觉有巨多细节,交了好多次都没过,罚掉了巨大多分。而旁边的 Kewth 已经过了 /kk
后来终于发现了错误,ok|=(!((sm-n)&1));
写成了 ok=(!((sm-n)&1));
,感觉数据要是水的话我就 FST 了 /kk
然后开 B ,什么屑数据结构,一通分析,写了个树状数组卡了巨久,不过一发过还是爽。
然后开 C ,突然发现 Imakf 说 1E 是原题,不过没有管太多继续在写,此时还有 1h 多,什么屑数位 dp 加巨大多分类讨论,莽了一发发现样例没过,发现代码中判断的是 \(a,b,c\) 构成三角形,赶忙改了回来,然后一发过。
然后我就不想写了, Imakf 又在说 1E ,此时我还没有意识到问题的严重性,于是抱着好奇的态度看了看 1E , woc 这不是考试题吗?然后我就开玩笑说要是我 copy 了那就直接 rank1 了,然后我就看了看榜,我之前一直在做题没有看榜,这不看不知道,一看吓一跳, 1E 的 AC 人数达到了几十将近一百!真差不多得了。
首页大部分人都来自同一个 country ,差不多得了,这就是中国场吗?爱了爱了,虽然我可以涨分,但这不 unrated CF 也差不多得了。
然后开 1D ,感觉是神仙构造题,和前三题画风都变了,好感度++,但是一直想不出来,其实也没有心思在想,看各种群都炸了,看来那道题目应该被作为考试题让中国选手沾尽了光, announcement 当时也被 down 到了七十多,于是我也跑去 down 了。
果然最后 div1 unr 了。
后面了解了情况,发现出题人两个 A 和 B, A 搬的题并且没有告诉其他人,然后让 B 发的 announcement ,现在已经被 down 到 -3000 多了,属于是见证历史了,结果 B 好心办的比赛 contribution 直接降到倒一,我看到也有 tester 在为 B 发话结果也被 down 惨了,我只能说交友不慎,恶意搬题不是素质问题吗???对 B 表示同情。
之前 announcement 说有一道原题被 tester 发现删掉了,我觉得我有理由怀疑那道题目也是 A 搬的。
Tutorial 一直拖到现在没有发。估计出题人直接被整自闭了。
不过 coordinator 的发言却被 up 到了 2300 多。
附上我会的三道题目的题解:
A Color the Picture
观察样例可以发现,同一颜色连通块必须上下穿过或者左右穿过,证明也很简单,因为有角就不行。
考虑上下穿过的连通块,它左右的长度必然大于等于 \(2\) ,也就是 \(a_i\ge 2n\) ,否则这种颜色就不能用。
对于每种颜色我们可以求出它左右跨度最大是多少,当且仅当所有可用颜色左右跨度最大为 \(2\) 时有额外要求 \(m\) 必须是偶数,正常要求就是所有颜色跨度和大于等于 \(m\) 。
左右穿过的连通块同理。
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define ch() getchar()
#define pc(x) putchar(x)
using namespace std;
template<typename T>void read(T&x){
static char c;static int f;
for(f=1,c=ch();c<'0'||c>'9';c=ch())if(c=='-')f=-f;
for(x=0;c>='0'&&c<='9';c=ch()){x=x*10+(c&15);}x*=f;
}
template<typename T>void write(T x){
static char q[64];int cnt=0;
if(x==0)return pc('0'),void();
if(x<0)pc('-'),x=-x;
while(x)q[cnt++]=x%10+'0',x/=10;
while(cnt--)pc(q[cnt]);
}
const int maxk=100005;
int a[maxk];
int main(){
int t;read(t);
while(t--){
int n,m,k;
read(n),read(m),read(k);
bool ok=false;
for(int i=1;i<=k;++i)
read(a[i]);
long long sm=0;bool fk=false;
for(int i=1;i<=k;++i){
if(a[i]>=n*2){
sm+=a[i]/n;
fk|=((a[i]/n)>=3);
}
}
if(sm>=m){
if(fk)ok|=true;
else ok|=(!((sm-m)&1));
}
sm=0;fk=false;
for(int i=1;i<=k;++i){
if(a[i]>=m*2){
sm+=a[i]/m;
fk|=((a[i]/m)>=3);
}
}
if(sm>=n){
if(fk)ok|=true;
else ok|=(!((sm-n)&1));
}
puts(ok?"Yes":"No");
}
return 0;
}
B Rain
屑分类讨论 + 数据结构题。
首先观察到性质:水位最高的位置一定是在输入的某个 \(x_i\) 中,因为要成为最大值必要条件是要成为极大值,对于某一次发水, \(x\) 左边的函数是下凸函数, \(x\) 右边的函数也是下凸函数,两个 \(x\) 中间的函数一定是若干个下凸函数的和——也是下凸函数,中间不可能有极大值。
于是先求出每个 \(x_i\) 的水位是多少。
对于 \(x_j\le x_i\) ,第 \(j\) 次发洪水对 \(x_i\) 的水位有贡献当且仅当 \(p_j\ge x_i-x_j\) ,也即 \(p_j+x_j\ge x_i\) ,且贡献为 \(p_j+x_j-x_i\) ,于是离散化后按照 \(x\) 排序从左往右扫,使用树状数组维护后缀 \(p_j+x_j\) 和以及 \(1\) 和,然后扫到 \(x_i\) 就进行一次查询得到前面的位置对 \(x_i\) 的贡献。
对于 \(x_j>x_i\) 类似。
每个位置的水位不妨设为 \(h_i\) ,那么当 \(h_i>m\) 的时候就需要减至少 \(h_i-m\) 的水,如果删除的一次发水 \(j\) 在 \(x_i\) 左侧,那么必须满足 \(p_j-(x_i-x_j)\ge h_i-m\) 即 \(p_j+x_j\ge h_i-m+x_i\) ;如果在右侧则必须满足 \(p_j-(x_j-x_i)\ge h_i-m\) 即 \(p_j-x_j\ge h_i-m-x_i\) ,一遍前缀最大值加后缀最大值即可确定每个位置 \(i\) 要满足的条件,然后就做完了。
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define inf 0x3f3f3f3f3f3f3f3fll
#define ch() getchar()
#define pc(x) putchar(x)
using namespace std;
template<typename T>void read(T&x){
static char c;static int f;
for(f=1,c=ch();c<'0'||c>'9';c=ch())if(c=='-')f=-f;
for(x=0;c>='0'&&c<='9';c=ch()){x=x*10+(c&15);}x*=f;
}
template<typename T>void write(T x){
static char q[64];int cnt=0;
if(x==0)return pc('0'),void();
if(x<0)pc('-'),x=-x;
while(x)q[cnt++]=x%10+'0',x/=10;
while(cnt--)pc(q[cnt]);
}
const int maxn=200005;
long long X[maxn],P[maxn];
long long TM[maxn*4],cn;
long long T1[maxn*4],T2[maxn*4];
void add(int p,long long _1,long long _2){
while(p){
T1[p]+=_1;T2[p]+=_2;
p^=p&(-p);
}
}
void ask(int p,long long&_1,long long&_2){
_1=_2=0;
while(p<=cn){
_1+=T1[p],_2+=T2[p];
p+=p&(-p);
}
}
long long H[maxn];
long long pre[maxn],suf[maxn];
struct FUCK{
long long x,p;int id;
FUCK(long long x=0,long long p=0,int id=0):
x(x),p(p),id(id){}
bool operator < (const FUCK o)const{
return x<o.x;
}
}fk[maxn];
int Ans[maxn];
int main(){
int t;read(t);
while(t--){
int n,m;
read(n),read(m);
cn=0;
for(int i=1;i<=n;++i){
read(X[i]),read(P[i]);
fk[i]=FUCK(X[i],P[i],i);
TM[cn++]=P[i]+X[i];
TM[cn++]=X[i];
TM[cn++]=P[i]-X[i];
TM[cn++]=-X[i];
H[i]=0;pre[i]=suf[i]=-inf;
}
sort(fk+1,fk+n+1);
for(int i=1;i<=n;++i)
X[i]=fk[i].x,P[i]=fk[i].p;
sort(TM,TM+cn);
cn=unique(TM,TM+cn)-TM;
for(int i=1;i<=cn;++i)
T1[i]=T2[i]=0;
for(int i=1;i<=n;++i){
add(lower_bound(TM,TM+cn,P[i]+X[i])-TM+1,P[i]+X[i],1);
long long _1=0,_2=0;ask(lower_bound(TM,TM+cn,X[i])-TM+1,_1,_2);
H[i]+=_1-_2*X[i];
}
for(int i=1;i<=cn;++i)
T1[i]=T2[i]=0;
for(int i=n;i>=1;--i){
long long _1=0,_2=0;ask(lower_bound(TM,TM+cn,-X[i])-TM+1,_1,_2);
H[i]+=_1+_2*X[i];
add(lower_bound(TM,TM+cn,P[i]-X[i])-TM+1,P[i]-X[i],1);
}
for(int i=1;i<=n;++i){
if(H[i]>m){
long long t=H[i]-m;
suf[i]=max(suf[i],t-X[i]);
pre[i]=max(pre[i],t+X[i]);
}
}
for(int i=n-1;i>=1;--i)
pre[i]=max(pre[i],pre[i+1]);
for(int i=2;i<=n;++i)
suf[i]=max(suf[i],suf[i-1]);
for(int i=1;i<=n;++i)
Ans[fk[i].id]=(P[i]-X[i]>=suf[i])&&(P[i]+X[i]>=pre[i]);
for(int i=1;i<=n;++i)
write(Ans[i]);
pc('\n');
}
return 0;
}
C XOR Triangle
屑数位 dp + 分类讨论。
首先 \(a,b,c\) 必须两两不同,如果 \(a=b\) 那么得到的三条边就是 \(0,a\oplus c,a\oplus c\) 显然不行,如果 \(a=b=c\) 那么三条边 \(0,0,0\) 也不行。
其次 \(a\oplus b,b\oplus c,c\oplus a\) 必须两两不同,如果 \(a\oplus b=b\oplus c\) ,那么 \(a=c\) 与 \(a,b,c\) 两两不同矛盾。
既然如此,同数位 dp 的思路,不妨钦定 \((a\oplus b)<(b\oplus c)<(c\oplus a)\) ,答案最后乘以 \(6\) 即可,这样不会算重,那么要满足的条件就是 \((a\oplus b)+(b\oplus c)>(c\oplus a)\) 。
设 \(dp(p,0/1,0/1,0/1,0/1,0/1,0/1,0/1)\) 表示从高到低考虑到了第 \(p\) 位,后面的 \(0/1\) 分别表示: \(a,b,c\) 有没有卡到上界,条件 \((a\oplus b)<(b\oplus c)\) 是否满足,条件 \((b\oplus c)<(c\oplus a)\) 是否满足, \((a\oplus b)+(b\oplus c)\) 是否必须进位, \((a\oplus b)+(b\oplus c)>(c\oplus a)\) 是否已经满足。
转移就枚举 \(a,b,c\) 第 \(p\) 位选的是啥,一通分类讨论即可。
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define ch() getchar()
#define pc(x) putchar(x)
using namespace std;
template<typename T>void read(T&x){
static char c;static int f;
for(f=1,c=ch();c<'0'||c>'9';c=ch())if(c=='-')f=-f;
for(x=0;c>='0'&&c<='9';c=ch()){x=x*10+(c&15);}x*=f;
}
template<typename T>void write(T x){
static char q[64];int cnt=0;
if(x==0)return pc('0'),void();
if(x<0)pc('-'),x=-x;
while(x)q[cnt++]=x%10+'0',x/=10;
while(cnt--)pc(q[cnt]);
}
const int maxn=200005,mod=998244353;
void UP(int&x,int y){x+=y;x-=(x>=mod?mod:0);}
int dp[maxn][2][2][2][2][2][2][2];
char s[maxn];
int dfs(int pos,int _1,int _2,int _3,int _m1,int _m2,int _a,int _ok){
int&re=dp[pos][_1][_2][_3][_m1][_m2][_a][_ok];if(~re)return re;
if(pos==0)return re=(_ok?6:0);
re=0;int o=s[pos]-'0',up1=(_1?o:1),up2=(_2?o:1),up3=(_3?o:1);
for(int i=0;i<=up3;++i)for(int j=0;j<=up2;++j)for(int k=0;k<=up1;++k)
if((_m1||((k^j)<=(j^i)))&&(_m2||((j^i)<=(i^k)))){
if(_ok)UP(re,dfs(pos-1,_1&&k==up1,_2&&j==up2,_3&&i==up3,(_m1||((k^j)<(j^i))),(_m2||((j^i)<(i^k))),_a,_ok));
else if(_a){
if((k^j)+(j^i)==2)UP(re,dfs(pos-1,_1&&k==up1,_2&&j==up2,_3&&i==up3,(_m1||((k^j)<(j^i))),(_m2||((j^i)<(i^k))),i,_ok));
else if((k^j)+(j^i)==1&&(i^k)==0)UP(re,dfs(pos-1,_1&&k==up1,_2&&j==up2,_3&&i==up3,(_m1||((k^j)<(j^i))),(_m2||((j^i)<(i^k))),_a,_ok));
}
else{
if((k^j)+(j^i)>(i^k))UP(re,dfs(pos-1,_1&&k==up1,_2&&j==up2,_3&&i==up3,(_m1||((k^j)<(j^i))),(_m2||((j^i)<(i^k))),_a,true));
else if((k^j)+(j^i)<(i^k))UP(re,dfs(pos-1,_1&&k==up1,_2&&j==up2,_3&&i==up3,(_m1||((k^j)<(j^i))),(_m2||((j^i)<(i^k))),true,_ok));
else UP(re,dfs(pos-1,_1&&k==up1,_2&&j==up2,_3&&i==up3,(_m1||((k^j)<(j^i))),(_m2||((j^i)<(i^k))),_a,_ok));
}
}
return re;
}
int main(){
memset(dp,-1,sizeof dp);
scanf("%s",s+1);int n=strlen(s+1);
reverse(s+1,s+n+1);
write(dfs(n,true,true,true,false,false,false,false));
return 0;
}