CSP-S2 2020 Solution
冷静思考,没有什么能拦住你!!!
T1----julian
Review
直接考察了程序组织实现能力。清晰的模块化构思会帮助实现。
强大的心理以及冷静分析相当重要。
关于实现的几点细节:
- 1582年10月15日前后适用的历法不同—主要不同在于闰年的判定。
- 有一段时间的删除:1582年10月5日 至 1582年10月14日。
- 公元0年不存在,即公元前1年后一年为公元1年。
- 公元前1年、公元前3年、公元前5年……为闰年。
- 输入数据需用long long 存储。。。
有不少人通过重重 if 判断处理这些细节,十分繁琐(听说有 5k + 2.5 h 的)
其实如果先想好,细节分几点、如何处理,就不用那么复杂了。
我的实现方式:
- 对于1、2两点细节,可以通过暴力找到分界点对应的儒略日,循环中不必再讨论过多。
- 对于3、4,则可以通过把公元前4713年设为公元-4712年解决,输出时简单判断就好。
- 发现历法有400年的周期,也可暴力出来。一年年跳后直接把一天天跳的暴力copy上就好。
Code
#include<cstdio>
#define ll long long
using namespace std;
const int d[13]={0,31,28,31,30,31,30,31,31,30,31,30,31};
int Q;
ll r;int Y,M,D;
ll k;
int yd,cy;
int main()
{
freopen("julian.in","r",stdin);
freopen("julian.out","w",stdout);
scanf("%d",&Q);
while (Q--)
{
scanf("%lld",&r);
Y=-4712;
M=1;
D=1;
if (r<=2299160)
{
k=r/146100;
r%=146100;
Y+=k*400;
while (r)
{
if (M==1||(M==2&&D<29))cy=Y;else cy=Y+1;
if (cy%4==0)yd=366;else yd=365;
if (r>=yd)
{
r-=yd;
++Y;
}else break;
}
if (r)
{
while (r)
{
if (M==2)
{
if (Y%4==0)
{
if (D==29)
{
++M;
D=1;
}else ++D;
}
else
{
if (D==28)
{
++M;
D=1;
}else ++D;
}
}else
{
if (D==d[M])
{
if (++M==13)M=1,++Y;
D=1;
}else ++D;
}
--r;
}
}
if (Y>0)printf("%d %d %d\n",D,M,Y);
else
{
printf("%d %d %d BC\n",D,M,-Y+1);
}
}else
{
r-=2299161;
Y=1582;
M=10;
D=15;
k=r/146097;
r%=146097;
Y+=k*400;
while (r)
{
if (M==1||(M==2&&D<29))cy=Y;else cy=Y+1;
if ((cy%400==0||(cy%4==0&&cy%100!=0)))yd=366;else yd=365;
if (r>=yd)
{
r-=yd;
++Y;
}else break;
}
if (r)
{
while (r)
{
if (M==2)
{
if (Y%400==0||(Y%4==0&&Y%100!=0))
{
if (D==29)
{
++M;
D=1;
}else ++D;
}
else
{
if (D==28)
{
++M;
D=1;
}else ++D;
}
}else
{
if (D==d[M])
{
if (++M==13)M=1,++Y;
D=1;
}else ++D;
}
--r;
}
}
printf("%d %d %d\n",D,M,Y);
}
}
return 0;
}
T2----Zoo
Review
考察阅读能力
考察语言掌握???好吧,还是有一点点技巧的吧。
几点细节:
- k可能为64,这使得不少变量需要用unsigned long long 存储。
- n可能为0,因此答案有可能会爆 ull —特判。
实现方式:
- a i a_i ai可以或起来后再算哪些位有1
- 老实打哈希—打了离散化的我T飞了(坏习惯)
- 变量类型
Code
#include<cstdio>
#include<algorithm>
#define N 1000005
#define ll unsigned long long
using namespace std;
int read()
{
int x=0;char ch=getchar();
while (ch<'0'||ch>'9')ch=getchar();
while (ch>='0'&&ch<='9')
{
x=x*10+(ch^48);
ch=getchar();
}
return x;
}
ll lread()
{
ll x=0;char ch=getchar();
while (ch<'0'||ch>'9')ch=getchar();
while (ch>='0'&&ch<='9')
{
x=x*10+(ch^48);
ch=getchar();
}
return x;
}
ll w[70];
int n,m,c,k;
bool bz[70];
int lk[70];
struct edge{
int to,nx;
}e[N];int tot;
inline void add(int u,int v)
{
e[++tot].to=v;e[tot].nx=lk[u];lk[u]=tot;
}
bool ab[70];
const int mo=19491001;
struct hash{
int h[mo];
inline int get(int x)
{
int p=x%mo;
while (h[p]&&h[p]!=x)
if (++p==mo)p=0;
return p;
}
inline void add(int x){h[get(x)]=x;}
inline bool in(int x){return h[get(x)];}
}H;
int main()
{
freopen("zoo.in","r",stdin);
freopen("zoo.out","w",stdout);
n=read();m=read();
c=read();k=read();
w[0]=1;
for (int i=1;i<k;++i)w[i]=w[i-1]<<1;
ll A=0;
for (int i=1;i<=n;++i)A|=lread();
for (int i=0;i<k;++i)
if (w[i]&A)bz[i]=1;
for (int i=1,p,q;i<=m;++i)
{
p=read();
q=read();
if (p<k)
{
add(p,q);
if (bz[p])
H.add(q);
}
}
for (int i=0;i<k;++i)
if (bz[i])ab[i]=1;
else
{
bool able=1;
for (int j=lk[i];j;j=e[j].nx)
if (!H.in(e[j].to))
{
able=0;
break;
}
if (able)ab[i]=1;
}
int sum=0;
for (int i=0;i<k;++i)sum+=ab[i];
if (sum==k)
{
if (k==64&&n==0)puts("18446744073709551616");
else printf("%llu\n",(w[k-1]-n)+w[k-1]);
}else
printf("%llu\n",w[sum]-n);
return 0;
}
T3----Call
Review
简单模型转化&运算简化
细节!?:
- 2操作可能会乘以0—听说有用的逆元做法?
- 一个函数的层层调用情况构成一个DAG
进行转化:
- 模型:图。
- 运算:
、、发现乘法只对全局操作,所有乘操作统一处理。加操作会被后面的乘操作影响,考虑乘操作影响如何快速统计。
、、可以发现,如果对于每次“函数调用”,都往下进行一次处理,那么时间复杂度无法保证。此时容易注意到,将不同时间调用的同类函数同时处理看上去可行(它们的DAG同构),而且复杂度似乎可行( ∑ c i ≤ 1 0 6 \sum c_i \leq 10^6 ∑ci≤106)。
、、怎么做?----分开考虑 1)函数调用的函数间的影响(乘操作,后同)以及 2)该(种)函数调用之后出现的函数对当前函数的影响。
、、把多次同种函数一起做?-> 将 2)影响的计算通过乘法分配律合并(往函数分支中计算时的转移都是乘以相同的数)。。。。一遍拓扑排序就没了?!是的。
难点就是一点转化,这就把不少人吓跑了。。
Code
#include<cstdio>
#define N 1000005
#define M 1000005
#define ll long long
#define mo 998244353
#define plus(x,y) if ((x+=(y))>=mo)x-=mo
using namespace std;
int read()
{
int x=0;char ch=getchar();
while (ch<'0'||ch>'9')ch=getchar();
while (ch>='0'&&ch<='9')
{
x=x*10+(ch^48);
ch=getchar();
}
return x;
}
bool sp[N],hf[N];
int n,a[N];
int m;
struct edge{
int to,nx;
ll w;
}e[M*6];int tot;
int d[N];
struct pro{
ll mul,Mul,Smul;
int lk,typ;
inline void add(int to,int w)
{
e[++tot].to=to;
if (typ==3)++d[to];
e[tot].nx=lk;
e[tot].w=w;
lk=tot;
}
}p[N];
int temp[M];
int f[N],Q;
void getMul(int x)
{
if (p[x].Mul||sp[x])return;
if (p[x].typ==1)
{
p[x].Mul=1;
return;
}else if (p[x].typ==2)
{
p[x].Mul=p[x].mul;
return;
}
p[x].Mul=1;
for (int i=p[x].lk;i;i=e[i].nx)
{
getMul(e[i].to);
if (sp[e[i].to])
{
sp[x]=1;
p[x].Mul=0;
return;
}
p[x].Mul=p[x].Mul*p[e[i].to].Mul%mo;
}
}
ll Smul=1;
ll b[N];
void solve(int x)
{
--d[x];
if (p[x].Smul)
{
if (p[x].typ==1)
{
plus(b[e[p[x].lk].to],(ll)e[p[x].lk].w*p[x].Smul%mo);
}else if (p[x].typ==2)
{
return;
}else
{
ll Mul=p[x].Smul;
for (int i=p[x].lk;i;i=e[i].nx)
{
plus(p[e[i].to].Smul,Mul%mo);
Mul=Mul*p[e[i].to].Mul%mo;
if (!--d[e[i].to])
solve(e[i].to);
}
}
}else if (p[x].typ==3)
{
for (int i=p[x].lk;i;i=e[i].nx)
if (!--d[e[i].to])solve(e[i].to);
}
}
int main()
{
freopen("call.in","r",stdin);
freopen("call.out","w",stdout);
n=read();
for (int i=1;i<=n;++i)a[i]=read();
m=read();
for (int i=1,x,y;i<=m;++i)
{
p[i].typ=read();
p[i].Smul=0;
if (p[i].typ==1)
{
p[i].mul=1;
x=read();
y=read();
p[i].lk=0;
p[i].add(x,y);
}else if (p[i].typ==2)
{
p[i].mul=read();
if (!p[i].mul)sp[i]=1;
p[i].lk=0;
}else
{
x=read();
p[i].lk=0;
for (int j=1;j<=x;++j)
p[i].add(read(),-1);
}
}
for (int i=1;i<=m;++i)getMul(i);
Q=read();
for (int i=1;i<=Q;++i)
{
f[i]=read();
Smul=Smul*p[f[i]].Mul%mo;
}
ll Mul=1;
for (int i=Q;i;--i)
{
plus(p[f[i]].Smul,Mul);
Mul=Mul*p[f[i]].Mul%mo;
}
for (int i=1;i<=m;++i)
if (!d[i])solve(i);
for (int i=1;i<=n;++i)
{
printf("%lld ",(Smul*a[i]+b[i])%mo);
}
return 0;
}
T4----Snakes
Review
小博弈题+实现技巧。
提供几条思考方向:
- 由于胜负与相对大小相联系,考虑模拟几轮游戏,看看与大小变化有关的规律----我建议不要总是带入(拿脚造的)样例数据思考,从宏观的游戏局面思考总是更加严谨全面。
- 直接从获胜(或取得更优状态)的充要条件出发推理各个情况。
以下“我”指编号为n的蛇
来推一波策略把:
1.
、、发现我吃了第一条蛇后的情况显然可以分为两种:1)我成了最弱的;2)我不是最弱的。(划分依据:直接与下一轮游戏的参与状况相联系)。
、、1)发现转化为判断下一位对手是否会吃我。而 2)情况似乎很复杂。不过我们可以继续1的思考方向—再模拟一轮。可以马上发现此轮吃蛇的蛇会比我更弱–>它会比我更早死;而它至少能靠停止吃蛇生存,那么我就不怕能不能吃蛇了。即遇到这种情况就一定会吃。
2.
、、仍然分出上面两种情况 1)2)。
、、1)若当前我吃了更优,那么下一轮我不能被吃,推理结果同上。
、、2)若当前我吃了更优,那么以后我不能被吃……推理结果同上。
两种情况的简化的博弈策略相结合,可以容易得到进一步的做法。
点一点实现技巧:与「noip2016 蚯蚓」做法类似。
由于每一轮游戏出现的新蛇体力单调递减,我们每次又只用找出体力最小/大的几条蛇,
新开一个队列就能完成有序队列的维护。
Code
#include<cstdio>
#include<algorithm>
#define N 1000005
using namespace std;
int read()
{
int x=0;char ch=getchar();
while (ch<'0'||ch>'9')ch=getchar();
while (ch>='0'&&ch<='9')
{
x=x*10+(ch^48);
ch=getchar();
}
return x;
}
typedef pair<int,int> P;
int n,T;
P a[N],b[N];
P operator -(P A,P B){return P(A.first-B.first,A.second);}
inline void solve()
{
int h1=1,t1=n,h2=1,t2=0,ans=n;
P mi,se,mx;
while (1)
{
if (t1-h1+t2-h2==0)
{
printf("%d\n",ans-1);
return;
}
mi=a[t1--];
if (h1<=t1&&a[t1]<b[t2]||h2>t2)se=a[t1];
else se=b[t2];
if (h1<=t1&&a[h1]>b[h2]||h2>t2)
{
if (se<a[h1]-mi)
{
++t2;
b[t2]=P(a[h1].first-mi.first,a[h1].second);
--ans;
++h1;
}else
{
P A=a[h1++]-mi;
bool sig=0;
while (A<se)
{
if (t1-h1+t2-h2+2==1)
{
sig^=1;
break;
}
if (h1<=t1&&a[h1]>b[h2]||h2>t2)A=a[h1++]-A;
else A=b[h2++]-A;
sig^=1;
}
if (!sig)--ans;
printf("%d\n",ans);
return;
}
}
else// 这部分好像不用,但我懒得改
{
if (se<b[h2]-mi)
{
++t2;
b[t2]=P(b[h2].first-mi.first,b[h2].second);
--ans;
++h2;
}else
{
P A=b[h2++]-mi;
bool sig=0;
while (A<se)
{
if (t1-h1+t2-h2+2==1)
{
sig^=1;
break;
}
if (h1<=t1&&a[h1]>b[h2]||h2>t2)A=a[h1++]-A;
else A=b[h2++]-A;
sig^=1;
}
if (!sig)--ans;
printf("%d\n",ans);
return;
}
}
}
}
int main()
{
freopen("snakes.in","r",stdin);
freopen("snakes.out","w",stdout);
T=read()-1;
n=read();
for (int i=1;i<=n;++i)a[n-i+1]=P(read(),i);
solve();
int k,x;
while (T--)
{
k=read();
while (k--)
{
x=read();
a[n-x+1].first=read();
}
solve();
}
return 0;
}
2++]-mi;
bool sig=0;
while (A<se)
{
if (t1-h1+t2-h2+2==1)
{
sig^=1;
break;
}
if (h1<=t1&&a[h1]>b[h2]||h2>t2)A=a[h1++]-A;
else A=b[h2++]-A;
sig^=1;
}
if (!sig)--ans;
printf("%d\n",ans);
return;
}
}
}
}
int main()
{
freopen("snakes.in","r",stdin);
freopen("snakes.out","w",stdout);
T=read()-1;
n=read();
for (int i=1;i<=n;++i)a[n-i+1]=P(read(),i);
solve();
int k,x;
while (T--)
{
k=read();
while (k--)
{
x=read();
a[n-x+1].first=read();
}
solve();
}
return 0;
}