CSP2020游记
Preface
游记永久挖坑,扔个题解跑路了……
发现自己心态是真的差,平时模拟题都扔给陈指导写,T1写了2.5h之后开T4伪了之后整个人完全无法思考了……
浑浑噩噩地出了考场才发现只穿了一件短袖的我已经满身是汗……
算是真正点醒了自己现在最大的两个问题:怕细节题、心态差,如果这次就是NOIp的话呢,真就3年OI见祖宗了?
才发现以前的自己之所以还能有不错的发挥是因为初三心态确实放得很好,但现在高二了一旦卡题整个人就完全不知所措了……
希望CSP成绩不会影响到省选吧,不然连省选都去不了了也感觉实在愧对自己……
儒略日
艹NM考场上没想到二分年份直接大力分类讨论直接把心态搞炸了
大体思路就是按\(400\)年一大循环,里面再分\(100\)年讨论,最后分\(4\)年讨论
最后确定年份之后直接大力模拟一遍即可,复杂度\(O(365T)\)
#include<cstdio>
#define RI register int
#define CI const int&
#define int long long
using namespace std;
const int trs[13]={0,31,28,31,30,31,30,31,31,30,31,30,31};
const int lim1=4713/4*(366+3*365)+366+1582/4*(3*365+366)+365;
int t,n,y,m,d,lim2,lim3,lim4,cur1,cur2;
inline void init(void)
{
RI i; for (i=1;i<10;++i) lim2+=trs[i]; lim2+=3;
lim3=trs[10]-14+trs[11]+trs[12]; lim4=365+(366+3*365)*4;
cur1=(366+3*365)*100-3; cur2=(366+3*365)*25;
}
inline void calc(int x,const bool& tp) //tp=1 run year
{
if (x==(365+tp)) return (void)(++y);
for (RI i=1;i<=x;++i) if (++d>(trs[m]+(m==2?tp:0))) d=1,++m;
}
inline void solve1(void)
{
y+=n/(366+3*365)*4; n%=(366+3*365);
if (n<=366) calc(n,1); else if (n<=366+365) y+=1,calc(n-366,0);
else if (n<=366+365*2) y+=2,calc(n-366-365,0); else y+=3,calc(n-366-365*2,0);
if (y<0) printf("%lld %lld %lld BC\n",d,m,-y); else printf("%lld %lld %lld\n",d,m,y+1);
}
inline void solve2(void)
{
if (n<=365) return (void)(calc(n,0),printf("%lld %lld %lld\n",d,m,y)); n-=365; ++y;
y+=n/(366+3*365)*4; n%=(366+3*365);
if (n<=366) calc(n,1); else if (n<=366+365) y+=1,calc(n-366,0);
else if (n<=366+365*2) y+=2,calc(n-366-365,0); else y+=3,calc(n-366-365*2,0);
printf("%lld %lld %lld\n",d,m,y);
}
signed main()
{
for (scanf("%lld",&t),init();t;--t)
{
scanf("%lld",&n); y=-4713; m=1; d=1;
if (n<=lim1+lim2) { solve1(); continue; } n-=lim1+lim2; y=1582; m=10; d=14;
if (n<=lim3) { calc(n,0); printf("%lld %lld %lld\n",d,m,y); continue; }
n-=lim3+1; y=1583; m=1; d=1; if (n<=lim4) { solve2(); continue; }
n-=lim4; y=1600; y+=n/cur1*400; n%=cur1;
if (n<=cur2)
{
y+=n/(365*3+366)*4; n%=(365*3+366);
if (n<=366) calc(n,1); else if (n<=366+365) y+=1,calc(n-366,0);
else if (n<=366+365*2) y+=2,calc(n-366-365,0); else y+=3,calc(n-366-365*2,0);
} else
{
y+=100; n-=cur2; y+=n/(cur2-1)*100; n%=(cur2-1);
if (n<=365) calc(n,0); else if (n<=365*2) y+=1,calc(n-365,0);
else if (n<=365*3) y+=2,calc(n-365*2,0); else if (n<=365*4) y+=3,calc(n-365*3,0);
else
{
y+=4; n-=365*4; y+=n/(365*3+366)*4; n%=(365*3+366);
if (n<=366) calc(n,1); else if (n<=366+365) y+=1,calc(n-366,0);
else if (n<=366+365*2) y+=2,calc(n-366-365,0); else y+=3,calc(n-366-365*2,0);
}
}
printf("%lld %lld %lld\n",d,m,y);
}
return 0;
}
动物园
组题人居心叵测啊,这如果和T1换一下大家都做得舒服
T4大样例死活过不去之后,还剩30min给T2,T3了,一眼没看出来是个SB题直接吓的写了暴力跑路
其实非常NT,我们考虑哪些位是可以取\(1\)的,显然只有有要求且这一位上有\(1\)的位以及所有没有要求的位
由于\(a_i\)相异,因此设上面的个数是\(c\),答案就是\(2^c-n\)
注意当\(n=m=0,k=64\)时会爆unsigned long long
,要特判一下
#include<cstdio>
#include<cctype>
#define RI register int
#define CI const int&
#define Tp template <typename T>
using namespace std;
int n,m,c,k,p,q,cur; unsigned long long x,ret; bool cvr[64],vis[64];
class FileInputOutput
{
private:
static const int S=1<<21;
#define tc() (A==B&&(B=(A=Fin)+fread(Fin,1,S,stdin),A==B)?EOF:*A++)
char Fin[S],*A,*B;
public:
Tp inline void read(T& x)
{
x=0; char ch; while (!isdigit(ch=tc()));
while (x=(x<<3)+(x<<1)+(ch&15),isdigit(ch=tc()));
}
#undef tc
}F;
int main()
{
RI i; F.read(n); F.read(m); F.read(c); F.read(k);
if (!n&&!m&&k==64) return puts("18446744073709551616"),0;
for (i=1;i<=n;++i) F.read(x),ret|=x;
for (i=1;i<=m;++i) if (F.read(p),F.read(q),vis[p]=1,(ret>>p)&1) cvr[p]=1;
for (i=0;i<k;++i) if (cur+=cvr[i],!vis[i]) ++cur;
return printf("%llu",(1ull<<cur)-n),0;
}
函数调用
TMD考场上写了一个要求逆元的暴力,结果没注意到会乘\(0\)直接全家升天(陈指导告诉没挂的暴力有\(70\)分)
今天稍微想了一下就会了,直接倒着做就可以避免这个问题了
首先我们考虑处理调用的问题,由于这里不会成环,因此显然可以建出一个DAG
我们注意到最后的答案显然可以表示成\(a_i\times mul+add_i\)的形式,我们发现我们可以把所有贡献都算到操作一上
具体地,我们维护出\(mul_i\)表示做到每个操作进行的全局乘法的值是多少,这个显然可以拓扑排序求
然后我们考虑在加法操作之后的乘法操作其实就相当于让加上的数乘上贡献
因此我们设\(cof_i\)表示第\(i\)个操作后面要进行的乘法操作次数,这个把边反着建做拓扑即可求出
综上,复杂度\(O(n+m+q)\),可以轻松通过
#include<cstdio>
#include<cctype>
#include<vector>
#define RI register int
#define CI const int&
#define Tp template <typename T>
#define pb push_back
using namespace std;
const int N=100005,mod=998244353;
vector <int> G[N],R[N]; int n,a[N],m,qs,tp[N],p[N],v[N],c,x,deg[N],mul[N],cof[N],q[N];
class FileInputOutput
{
private:
static const int S=1<<21;
#define tc() (A==B&&(B=(A=Fin)+fread(Fin,1,S,stdin),A==B)?EOF:*A++)
char Fin[S],*A,*B;
public:
Tp inline void read(T& x)
{
x=0; char ch; while (!isdigit(ch=tc()));
while (x=(x<<3)+(x<<1)+(ch&15),isdigit(ch=tc()));
}
#undef tc
}F;
inline void Top_Sort1(void)
{
RI i,H=0,T=0; for (i=0;i<=m;++i) if (!(deg[i]=R[i].size())) q[++T]=i;
while (H<T)
{
int now=q[++H],len=G[now].size(),to; for (i=0;i<len;++i)
if (to=G[now][i],mul[to]=1LL*mul[to]*mul[now]%mod,!(--deg[to])) q[++T]=to;
}
}
inline void Top_Sort2(void)
{
RI i,H=0,T=0; for (i=0;i<=m;++i) if (!(deg[i]=G[i].size())) q[++T]=i; cof[0]=1;
while (H<T)
{
int now=q[++H],len=R[now].size(),to,cur=1; for (i=len-1;~i;--i)
if (to=R[now][i],(cof[to]+=1LL*cof[now]*cur%mod)%=mod,cur=1LL*cur*mul[to]%mod,!(--deg[to])) q[++T]=to;
}
}
int main()
{
RI i; for (F.read(n),i=1;i<=n;++i) F.read(a[i]);
for (F.read(m),i=1;i<=m;++i) switch (F.read(tp[i]),tp[i])
{
case 1: F.read(p[i]); F.read(v[i]); mul[i]=1; break;
case 2: F.read(mul[i]); break;
case 3: for (mul[i]=1,F.read(c);c;--c) F.read(x),G[x].pb(i),R[i].pb(x); break;
}
for (F.read(qs),mul[0]=i=1;i<=qs;++i) F.read(x),G[x].pb(0),R[0].pb(x);
for (Top_Sort1(),Top_Sort2(),i=1;i<=n;++i) a[i]=1LL*a[i]*mul[0]%mod;
for (i=1;i<=m;++i) if (tp[i]==1) (a[p[i]]+=1LL*v[i]*cof[i]%mod)%=mod;
for (i=1;i<=n;++i) printf("%d ",a[i]); return 0;
}
贪吃蛇
考场上除了T1基本上都在做这题了,结果最后没想清楚导致直接升天
考虑此时体力值最大的蛇\(x\),如果它吃了体力值最小的蛇\(y\)后体力值不是剩下的最小的话一定可以吃
证明:之后当时体力值最大的蛇\(x'\),若它选择吃掉此时体力值最小的蛇\(y'\)的话,由于\(x'<x,y'>y\Rightarrow x'-y'<x-y\),即它此时的体力值一定比\(x\)剩下的小。若之后\(y\)有可能被吃掉的话也必然在\(x\)之前,那么此时它必然会后悔自己当初的行为,选择不吃游戏结束。
因此无论\(y\)吃不吃,\(x\)都不会被吃,故得证
然后当时naive了直接这样写了,但是忽略了一种情况(我说和答案怎么就差\(1\)):
若\(x\)吃了后体力值变为最小,那么对于之后体力值最大的\(y\)来说,若它吃了\(x\)之后体力值不变最小的话显然是会吃的,否则若吃了\(y\)体力值变最小的话继续讨论
设此时体力值最大的\(z\),若它吃了\(y\)之后体力值不变最小的话显然是会吃的,否则继续讨论……
我们发现这个东西一定会在只剩一条蛇或当某条蛇吃了之后没变最小的时候结束,假设最后一个选择吃的是\(z\),那么我们发现此时作为\(y\)就不能吃,那么就可以推出\(x\)是可以吃的
很容易发现我们只需要统计出这样的情形下蛇的数量,根据奇偶数就可以判断\(x\)是否可以吃了
同时注意到此时要么\(x\)选择不吃游戏结束,要么\(x\)选择吃\(y\)选择不吃游戏结束,因此复杂度是有保证的
显然可以直接用set
大力模拟上面的过程,复杂度\(O(Tn\log n)\),在Luogu上吸氧可以通过
#include<cstdio>
#include<cctype>
#include<set>
#define RI register int
#define CI const int&
#define Tp template <typename T>
using namespace std;
const int N=1e6+5;
struct element
{
int val,pos;
inline element(CI Val=0,CI Pos=0) { val=Val; pos=Pos; }
friend inline bool operator < (const element& A,const element& B)
{
return A.val!=B.val?A.val<B.val:A.pos<B.pos;
}
};
int t,n,a[N],k,x,y; multiset <element> s;
class FileInputOutput
{
private:
static const int S=1<<21;
#define tc() (A==B&&(B=(A=Fin)+fread(Fin,1,S,stdin),A==B)?EOF:*A++)
char Fin[S],*A,*B;
public:
Tp inline void read(T& x)
{
x=0; char ch; while (!isdigit(ch=tc()));
while (x=(x<<3)+(x<<1)+(ch&15),isdigit(ch=tc()));
}
#undef tc
}F;
inline int solve(void)
{
s.clear(); for (RI i=1;i<=n;++i) s.insert(element(a[i],i));
for (;;)
{
if (s.size()<=2) return 1; element nxt((--s.end())->val-s.begin()->val,(--s.end())->pos);
s.erase(s.begin()); s.erase(--s.end()); if (nxt<*s.begin())
{
int sz=s.size()+2,cur=1; for (s.insert(nxt);;)
{
if (s.size()<=2) break; element nxt((--s.end())->val-s.begin()->val,(--s.end())->pos);
s.erase(s.begin()); s.erase(--s.end()); if (nxt<*s.begin())
s.insert(nxt),++cur; else break;
}
return sz-(cur+1)%2;
} else s.insert(nxt);
}
}
int main()
{
RI i; for (F.read(t),F.read(n),i=1;i<=n;++i) F.read(a[i]);
for (printf("%d\n",solve()),--t;t;--t)
{
for (F.read(k),i=1;i<=k;++i) F.read(x),F.read(y),a[x]=y;
printf("%d\n",solve());
}
return 0;
}
然后我们根据前面的证明过程以及结合NOIp2016的蚯蚓很容易发现可以用两个双端队列来维护
一个用来维护所有未被操作过的蛇,一个用来维护所有被操作过的蛇,此时两边都是具有单调性的,复杂度的\(\log\)就去掉了
#include<cstdio>
#include<cctype>
#include<queue>
#define RI register int
#define CI const int&
#define Tp template <typename T>
using namespace std;
const int N=1e6+5;
struct element
{
int val,pos;
inline element(CI Val=0,CI Pos=0) { val=Val; pos=Pos; }
friend inline bool operator < (const element& A,const element& B)
{
return A.val!=B.val?A.val<B.val:A.pos<B.pos;
}
};
int t,n,a[N],k,x,y; deque <element> q1,q2;
class FileInputOutput
{
private:
static const int S=1<<21;
#define tc() (A==B&&(B=(A=Fin)+fread(Fin,1,S,stdin),A==B)?EOF:*A++)
char Fin[S],*A,*B;
public:
Tp inline void read(T& x)
{
x=0; char ch; while (!isdigit(ch=tc()));
while (x=(x<<3)+(x<<1)+(ch&15),isdigit(ch=tc()));
}
#undef tc
}F;
inline element getmin(CI tp,element cur=element())
{
if (!q1.empty()&&(q2.empty()||q1.front()<q2.front())) cur=q1.front(),tp&&(q1.pop_front(),0);
else cur=q2.front(),tp&&(q2.pop_front(),0); return cur;
}
inline element getmax(CI tp,element cur=element())
{
if (!q1.empty()&&(q2.empty()||q2.back()<q1.back())) cur=q1.back(),tp&&(q1.pop_back(),0);
else cur=q2.back(),tp&&(q2.pop_back(),0); return cur;
}
inline int solve(void)
{
q1.clear(); q2.clear(); for (RI i=1;i<=n;++i) q1.push_back(element(a[i],i));
for (;;)
{
if (q1.size()+q2.size()<=2) return 1; element mi=getmin(1),mx=getmax(1);
element nxt(mx.val-mi.val,mx.pos); if (nxt<getmin(0))
{
int sz=q1.size()+q2.size()+2,cur=1; for (q2.push_front(nxt);;)
{
if (q1.size()+q2.size()<=2) break;
element mi=getmin(1),mx=getmax(1),nxt(mx.val-mi.val,mx.pos);
if (nxt<getmin(0)) q2.push_front(nxt),++cur; else break;
}
return sz-(cur+1)%2;
} else q2.push_front(nxt);
}
}
int main()
{
RI i; for (F.read(t),F.read(n),i=1;i<=n;++i) F.read(a[i]);
for (printf("%d\n",solve()),--t;t;--t)
{
for (F.read(k),i=1;i<=k;++i) F.read(x),F.read(y),a[x]=y;
printf("%d\n",solve());
}
return 0;
}
Postscript
NOIp2020,Bless All……