CSP 2020 S 题解
因为最近忙于 whk ,所以时间不充裕,现在才补完题。
因为爆炸的有点厉害,所以还是详细一些吧。
\(\text{T}1.\)
这个一眼就是大模拟了,我们考虑处理一下特殊的:
\(1.\) 公元前年份,闰年的方式比较神奇,打表求第一个断点;
\(2.\) 处理一下 \(-1\sim 1\) 年,再打表到 \(1582.10.4\);
\(3.\) 处理一下时间跳动,然后到 \(1600.12.31\)。
然后对于小于表限制的时间,直接输出,其他的,考虑二分,闰年判断很特殊,总的计算公式有:
然后注意一下边界,别像我那样自作聪明特判了一下 \(12.31\) 结果正好错了/kk。
总的复杂度是 \(\mathcal O(2305813+Q\log r)\)。
代码十分冗长,但是能过(
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<queue>
#include<vector>
#include<map>
#include<cstring>
#include<ctime>
#define ll long long
using namespace std;
#define MAXN 5000005
#define read(x) scanf("%d",&x)
int q;
ll x;
int lsd,lsm,lsy;
struct node
{
int y,m,d;
}ans[MAXN];
int main()
{
int d=1,m=1,y=-4713;
ans[0].d=1,ans[0].m=1,ans[0].y=-4713;
for(int i=1;i<=1721424;i++)
{
d++;
if(m==1||m==3||m==5||m==7||m==8||m==10||m==12)
{
if(d==32) d=1,m++;
}
else if(m!=2)
{
if(d==31) d=1,m++;
}
else
{
if((-y)%4==1&&d==30)
{
d=1,m=3;
}
else if((-y)%4!=1&&d==29)
{
d=1,m=3;
}
}
if(m>12) m=1,d=1,y++;
ans[i].d=d,ans[i].y=y,ans[i].m=m;
}
ans[1721424].y=1;
y=1,d=1,m=1;
for(int i=1721425;i<=2299160;i++)
{
d++;
if(m==1||m==3||m==5||m==7||m==8||m==10||m==12)
{
if(d==32) d=1,m++;
}
else if(m!=2)
{
if(d==31) d=1,m++;
}
else
{
if(y%4==0&&d==30) d=1,m=3;
else if(y%4!=0&&d==29) d=1,m=3;
}
if(m>12) m=1,d=1,y++;
ans[i].d=d,ans[i].y=y,ans[i].m=m;
}
ans[2299161].d=15,ans[2299161].y=1582,ans[2299161].m=10;
d=15,m=10,y=1582;
for(int i=2299162;i<=2305813;i++)
{
d++;
if(m==1||m==3||m==5||m==7||m==8||m==10||m==12)
{
if(d==32) d=1,m++;
}
else if(m!=2)
{
if(d==31) d=1,m++;
}
else if(m==2)
{
if(y%4!=0)
{
if(d==29) d=1,m=3;
}
else
{
if(y%100!=0)
{
if(d==30) d=1,m=3;
}
else
{
if(y%400!=0)
{
if(d==29) d=1,m=3;
}
else
{
if(d==30) d=1,m=3;
}
}
}
}
if(m>12) m=1,d=1,y++;
ans[i].d=d,ans[i].y=y,ans[i].m=m;
}
read(q);
for(int i=1;i<=q;i++)
{
scanf("%lld",&x);
if(x<=2305813)
{
printf("%d %d ",ans[x].d,ans[x].m);
if(ans[x].y<0)
{
printf("%d BC\n",-ans[x].y);
}
else printf("%d\n",ans[x].y);
}
else
{
x-=2305813;
int l=1601,r=1000000001,mid;
ll dd;
while(l<r)
{
mid=(l+r)>>1;
int fe=(mid-1601);
int op=(fe/4)-(fe/100)+(fe/400);
dd=(1ll*op*366ll+1ll*(fe-op)*365ll);
if(dd>=x) r=mid;
else l=mid+1;
}
int fe=(l-1601);
int op=(fe/4)-(fe/100)+(fe/400);
dd=(1ll*op*366ll+1ll*(fe-op)*365ll);
if(dd>=x) l--;
int f=0;
m=0,d=0;
if(l%400==0||(l%4==0&&l%100!=0)) f=1;
fe=(l-1601);
op=(fe/4)-(fe/100)+(fe/400);
dd=(1ll*op*366ll+1ll*(fe-op)*365ll);
int now=0,df=x-dd;
for(int j=1;j<=12;j++)
{
if(j==1||j==3||j==5||j==7||j==8||j==10||j==12)
{
now+=31;
if(df<=now)
{
df=df-now+31;
m=j;
break;
}
}
else if(j!=2)
{
now+=30;
if(df<=now)
{
df=df-now+30;
m=j;
break;
}
}
else
{
if(f) now+=29;
else now+=28;
if(df<=now)
{
if(f) df=df-now+29;
else df=df-now+28;
m=j;
break;
}
}
}
for(int i=1;;i++)
{
df--;
if(!df)
{
d=i;
break;
}
}
printf("%d %d %d\n",d,m,l);
}
}
return 0;
}
这道题应该是考场策略题,按照比较有能力的 OIer 水平,在一个小时内做出应该是必须保证的吧(
当然,我不得不承认自己没能力,而且码力更差,但是 \(70\sim 80\) 分的表是个人都能吧。
然而我却删掉了 \(60\) 分改成了 \(40\) 分,将断点为 \(1585\) 年的错误程序交上,甚至连天天提醒的特判都没想到,导致 \(r=0\) 直接爆炸,我也是没谁了。
\(\text{T} 2.\)
这是这场比赛的签到题。
我们考虑什么时候回造成限制。
当然是有对这一位要求的食材,但是没有这一位是 \(1\) 的动物。
把这些位减去,剩下的位可以随便填,根据组合基本知识可知是 \(2^{cnt}\) (\(cnt\) 是能随便放的位置)。
当然,为了避免重复,我们考虑将 \(n\) 个已经给出的,互不相同的数个数减掉。
于是容易想到按位考虑,时间复杂度可以做到 \(\mathcal O(nk)\)。
有比较多的细节。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<queue>
#include<vector>
#include<map>
#include<cstring>
#include<ctime>
#define ll long long
using namespace std;
#define MAXN 1000005
#define ull unsigned long long
#define read(x) scanf("%d",&x)
int n,m,c,k;
ull a[MAXN];
int cnt[100],vis[100];
int p,q,tot;
int main()
{
read(n),read(m),read(c),read(k),tot=k;
if(!n&&!m&&k==64) return puts("18446744073709551616"),0;
for(int i=1;i<=n;i++)
{
scanf("%llu",&a[i]);
for(int j=k-1;j>=0;j--)
{
if(a[i]&(1ull<<j)) cnt[j]++;
}
}
for(int i=1;i<=m;i++)
{
read(p),read(q);
if(!cnt[p]&&!vis[p])
{
tot--;
vis[p]=1;
}
}
if(tot==64) printf("%llu\n",(1ull<<63)-(ull)n+(1ull<<63));
else printf("%llu\n",(1ull<<tot)-(ull)n);
return 0;
}
同样,刚做到此题时,不断提醒自己用 long long
做实验后改成 unsigned long long
,甚至还有那个很神仙的特判我都想到了。
可是心态不太好,写完就忘改了。
我想不到左移 \(64\) 位拆成两次就算了,可以为什么可以最后改文件输入输出没有编译一下就交上导致不知道为什么手残把 for
改成了 fo
到赛后才知道直接没 \(85\) 分这样啊,你还是退役罢。
\(\text{T} 3.\)
就我感觉这道题是最神仙的吗,我是废柴。
因为只剩下半小时写后两道题,所以只写了暴力。
赛后来考虑这道题,想到是反向建边,然后处理每个操作 \(1\) 的贡献。
具体是要处理全局乘后缀积,然后在一个位置执行操作 \(1\) 会被他后面的所影响。
然而还有一种情况,就是在执行同一个操作向下递归时,操作 \(1\) 会被同一时刻上的后面执行的乘法所影响,这正是后缀积所忽略的情况,也是反向建图所不能解决的问题。
所以考虑计算没一个执行的操作向下的影响。
处理后缀积的方法显然是不变的。
我们考虑乘法的意义,乘 \(n\) 就相当于加 \(n\) 次。
我们考虑 \(dp\)。
在图上,我们设 \(dp_i\) 为目前 \(i\) 点,下的所有加法操作,最后都要乘上 \(dp_i\),或者理解为执行 \(dp_i\) 次。
我们先处理一下给出的调用点上的 \(dp\) 值,显然就是后缀积。
这相当于 \(dp\) 的初始化。
由于前向星加边顺序和枚举边顺序的倒序性,我没枚举出边是就相当于倒叙处理。
容易想到维护一个系数,代表着靠前执行但是属于一个执行来源(或者是说宏观时间相同),后面的对前面的系数影响。
容易考虑维护 \(f_i\) 代表执行一下 \(i\) 点,递归后会乘上多少。
然后维护同一时间的系数可以表示为后面执行的下一步的 \(f_i\) 之积,我们设某一时刻的系数为 \(mll\),则有递推式:
然后发现是裸的 DAG 上 \(dp\),可以用拓扑排序来解决,时间复杂度是 \(\mathcal O(n+\sum C+Q)\)。
#include"iostream"
#include"cstdio"
#include"queue"
using namespace std;
#define int long long
#define read(x) scanf("%lld",&x)
#define MAXN 100005
#define MOD 998244353
int n,m,q;
int c,x,a[MAXN];
struct node
{
int to,nxt;
}e[MAXN*10];
int head[MAXN],cnt=0;
int mul[MAXN],f[MAXN];
int ty[MAXN],pos[MAXN],fac[MAXN];
int b[MAXN],deg[MAXN],dp[MAXN],ad[MAXN];
int vis[MAXN];
queue<int>qu;
void add(int u,int v){e[++cnt].to=v,e[cnt].nxt=head[u],head[u]=cnt;}
void dfs(int cur)
{
vis[cur]=mul[cur]=1ll;
if(ty[cur]==2) mul[cur]=fac[cur];
for(int i=head[cur];i;i=e[i].nxt)
{
int j=e[i].to;
if(!vis[j]) dfs(j);
mul[cur]=mul[cur]*mul[j]%MOD;
}
return;
}
signed main()
{
read(n);
for(int i=1;i<=n;i++) read(a[i]);
read(m);
for(int i=1;i<=m;i++)
{
read(ty[i]);
if(ty[i]==1) read(pos[i]),read(fac[i]);
else if(ty[i]==2) read(fac[i]);
else
{
read(c);
for(int j=1;j<=c;j++) read(x),add(i,x),deg[x]++;
}
}
for(int i=1;i<=m;i++) if(!vis[i]) dfs(i);
read(q);
for(int i=1;i<=q;i++) read(b[i]);
f[q+1]=1ll,dp[b[q]]=1ll;
for(int i=q;i>=1;i--)
{
f[i]=f[i+1]*mul[b[i]]%MOD;
dp[b[i-1]]=(dp[b[i-1]]+f[i])%MOD;
}
for(int i=1;i<=m;i++) if(!deg[i]) qu.push(i);
while(!qu.empty())
{
int u=qu.front();
qu.pop();
if(ty[u]==1) ad[pos[u]]=(ad[pos[u]]+fac[u]*dp[u]%MOD)%MOD;
int mll=1ll;
for(int i=head[u];i;i=e[i].nxt)
{
int j=e[i].to;
dp[j]=(dp[j]+mll*dp[u]%MOD)%MOD;
mll=mll*mul[j]%MOD;
deg[j]--;
if(!deg[j]) qu.push(j);
}
}
for(int i=1;i<=n;i++)
{
a[i]=a[i]*f[1]%MOD+ad[i];
printf("%lld ",a[i]%MOD);
}
return puts(""),0;
}
考场上暴力几乎一遍对,是这次最给力的了。
\(\text{T}4.\)
感觉这题应该没有黑题难度,因为我是不可能自己做出黑题的(虽然是考场外)。
我们发现,两只蛇如果大的吃掉小的后仍然最大,那就吃呗。
如果不是最大,也不是最小,把新的那个记为 \(x_n-x_1\)。
那么这时候下一组是 \(x_{n-1},x_2\)
我们使两者相减,有 \(x_{n}-x_1-x_{n-1}+x_{2}>0\),说明那一个一定不会因后面的而沦落成最小而被吃,也就是说,可以继续吃。
如果成为了最小就一定不吃吗?
显然不是。
如果新的最大蛇不敢吃它,那他就能吃。
那什么时候不敢呢?
显然是吃掉之后成为了最小的,而且下一个最小蛇敢吃。
emmm..... 你发现是一个循环的因果报应,所以算一下奇偶性就好了。
这个东西可以平衡树(直言。
当然不值,我们考虑堆(请原谅我不会 set
),一个是大根堆,另一个是小根堆,我们只是删除大根堆里最大的,小根堆里最小的,然后用一个数字记录下理论剩余数字个数就行了。
容易证明这两个堆互不影响。
这个复杂度是 \(\mathcal O(Tn\log n)\),有 \(70\) 分,挺多的。
#include"iostream"
#include"cstdio"
#include"cmath"
#include"queue"
using namespace std;
#define MAXN 1000005
#define read(x) scanf("%d",&x)
int t,n,m,x,y;
int a[MAXN],cnt,tot;
priority_queue<pair<int,int>,vector<pair<int,int> >,greater<pair<int,int> > >q;
priority_queue<pair<int,int> > p;
int nu[5];
int main()
{
read(t);
read(n),t-=1;
for(int i=1;i<=n;i++) read(a[i]),p.push(make_pair(a[i],i)),q.push(make_pair(a[i],i));
cnt=n;
while(1)
{
int maxn=p.top().first,minx=q.top().first;
nu[1]=p.top().second,nu[2]=q.top().second;
p.pop(),q.pop();
if(cnt==2)
{
puts("1");
q.pop(),p.pop();
q.pop(),p.pop();
break;
}
int minxx=q.top().first;
nu[3]=q.top().second;
if(maxn-minx<minxx||(maxn-minx==minxx&&nu[3]>nu[1]))
{
tot=cnt-1;
q.push(make_pair(maxn-minx,nu[1])),p.push(make_pair(maxn-minx,nu[1]));
int op=0;
while(1)
{
maxn=p.top().first,minx=q.top().first;
nu[1]=p.top().second,nu[2]=q.top().second;
q.pop(),p.pop(),tot-=2;
if(!tot)
{
if(op%2==1) cnt--;
break;
}
minxx=q.top().first;
nu[3]=q.top().second;
if(maxn-minx>minxx||(maxn-minx==minxx&&nu[3]<nu[1]))
{
if(op%2==1) cnt--;
break;
}
op++,tot++;
q.push(make_pair(maxn-minx,nu[1])),p.push(make_pair(maxn-minx,nu[1]));
}
printf("%d\n",cnt);
while(!q.empty()) q.pop();
while(!p.empty()) p.pop();
break;
}
int now=maxn-minx;
q.push(make_pair(now,nu[1])),p.push(make_pair(now,nu[1]));
cnt--;
}
while(t--)
{
read(m);
for(int i=1;i<=m;i++)
{
read(x),read(y);
a[x]=y;
}
cnt=n;
for(int i=1;i<=n;i++) p.push(make_pair(a[i],i)),q.push(make_pair(a[i],i));
while(1)
{
int maxn=p.top().first,minx=q.top().first;
nu[1]=p.top().second,nu[2]=q.top().second;
p.pop(),q.pop();
if(cnt==2)
{
puts("1");
q.pop(),p.pop();
q.pop(),p.pop();
break;
}
int minxx=q.top().first;
nu[3]=q.top().second;
if(maxn-minx<minxx||(maxn-minx==minxx&&nu[3]>nu[1]))
{
tot=cnt-1;
q.push(make_pair(maxn-minx,nu[1])),p.push(make_pair(maxn-minx,nu[1]));
int op=0;
while(1)
{
maxn=p.top().first,minx=q.top().first;
nu[1]=p.top().second,nu[2]=q.top().second;
q.pop(),p.pop(),tot-=2;
if(!tot)
{
if(op%2==1) cnt--;
break;
}
minxx=q.top().first;
nu[3]=q.top().second;
if(maxn-minx>minxx||(maxn-minx==minxx&&nu[3]<nu[1]))
{
if(op%2==1) cnt--;
break;
}
op++,tot++;
q.push(make_pair(maxn-minx,nu[1])),p.push(make_pair(maxn-minx,nu[1]));
}
printf("%d\n",cnt);
while(!q.empty()) q.pop();
while(!p.empty()) p.pop();
break;
}
int now=maxn-minx;
q.push(make_pair(now,nu[1])),p.push(make_pair(now,nu[1]));
cnt--;
}
}
return 0;
}
然后我发现这个东西单调性好像还挺明显的,那就试一试吧。
还记得我们刚才证明的 \(x_n-x_1>x_{n-1}-x_2\) 吗,我们推广一下,对于新数既不是最大也不是最小的,生成的新数也有单调性,于是维护一个双端队列,其实就是一个单调队列,单调性不请自来的那种,然后从原数组和队列中比对选最值即可。
时间复杂度就是 \(\mathcal O(Tn)\) 啦!
注意我们更新是原数组随着插入、删除已面貌全非,所以要备份一下,这样就可以保证下一次可以直接替换了。
真好。
#include"cstring"
#include"iostream"
#include"cstdio"
#include"cmath"
#include"queue"
using namespace std;
#define MAXN 1000005
#define int long long
#define read(x) scanf("%d",&x)
int t,n,m,x,y;
int a[MAXN],b[MAXN],tt[MAXN],vis[MAXN];
int l,r;
int head=0,tail=1;
struct node
{
int val,id;
}q[MAXN*2];
int maxn,minx,minxx;
int cnt,tot;
int num1,num2;
signed main()
{
read(t),t--;
read(n),l=1,r=n,cnt=n;
for(int i=1;i<=n;i++) read(a[i]),b[i]=i,tt[i]=a[i];
while(1)
{
if(head>=tail)
{
if(q[tail].val>a[r]||(q[tail].val==a[r]&&q[tail].id>b[r]))
num1=q[tail].id,maxn=q[tail++].val;
else num1=b[r],maxn=a[r--];
if(q[head].val<a[l]||(q[head].val==a[l]&&q[head].id<b[l]))
minx=q[head--].val;
else minx=a[l++];
}
else num1=b[r],maxn=a[r--],minx=a[l++];
cnt-=2;
if(!cnt)
{
puts("1");
break;
}
if(head>=tail)
{
if(q[head].val<a[l]||(q[head].val==a[l]&&q[head].id<b[l]))
num2=q[head].id,minxx=q[head].val;
else num2=b[l],minxx=a[l];
}
else num2=b[l],minxx=a[l];
if(maxn-minx<minxx||(maxn-minx==minxx&&num1<num2))
{
tot=cnt+1;
a[--l]=maxn-minx,b[l]=num1;
int op=0;
while(1)
{
if(head>=tail)
{
if(q[tail].val>a[r]||(q[tail].val==a[r]&&q[tail].id>b[r]))
num1=q[tail].id,maxn=q[tail++].val;
else num1=b[r],maxn=a[r--];
if(q[head].val<a[l]||(q[head].val==a[l]&&q[head].id<b[l]))
minx=q[head--].val;
else minx=a[l++];
}
else num1=b[r],maxn=a[r--],minx=a[l++];
tot-=2;
if(!tot)
{
if(op%2==1) cnt--;
break;
}
if(head>=tail)
{
if(q[head].val<a[l]||(q[head].val==a[l]&&q[head].id<b[l]))
num2=q[head].id,minxx=q[head].val;
else num2=b[l],minxx=a[l];
}
else num2=b[l],minxx=a[l];
if(maxn-minx>minxx||(maxn-minx==minxx&&num1>num2))
{
if(op%2==1) cnt--;
break;
}
op++,tot++;
a[--l]=maxn-minx,b[l]=num1;
}
printf("%d\n",cnt+2);
break;
}
else if(maxn-minx>a[r]||(maxn-minx==a[r]&&num1>b[r]))
{
a[++r]=maxn-minx;
b[r]=num1;
}
else q[++head].val=maxn-minx,q[head].id=num1;
cnt++;
}
while(t--)
{
l=1,r=n,cnt=n;
head=0,tail=1;
memset(q,0,sizeof(q));
memset(vis,0,sizeof(vis));
read(m);
for(int i=1;i<=m;i++)
{
read(x),read(y);
a[x]=y,vis[x]=1;
}
for(int i=1;i<=n;i++)
{
b[i]=i;
if(!vis[i]) a[i]=tt[i];
tt[i]=a[i];
}
while(1)
{
if(head>=tail)
{
if(q[tail].val>a[r]||(q[tail].val==a[r]&&q[tail].id>b[r]))
num1=q[tail].id,maxn=q[tail++].val;
else num1=b[r],maxn=a[r--];
if(q[head].val<a[l]||(q[head].val==a[l]&&q[head].id<b[l]))
minx=q[head--].val;
else minx=a[l++];
}
else num1=b[r],maxn=a[r--],minx=a[l++];
cnt-=2;
if(!cnt)
{
puts("1");
break;
}
if(head>=tail)
{
if(q[head].val<a[l]||(q[head].val==a[l]&&q[head].id<b[l]))
num2=q[head].id,minxx=q[head].val;
else num2=b[l],minxx=a[l];
}
else num2=b[l],minxx=a[l];
if(maxn-minx<minxx||(maxn-minx==minxx&&num1<num2))
{
tot=cnt+1;
a[--l]=maxn-minx,b[l]=num1;
int op=0;
while(1)
{
if(head>=tail)
{
if(q[tail].val>a[r]||(q[tail].val==a[r]&&q[tail].id>b[r]))
num1=q[tail].id,maxn=q[tail++].val;
else num1=b[r],maxn=a[r--];
if(q[head].val<a[l]||(q[head].val==a[l]&&q[head].id<b[l]))
minx=q[head--].val;
else minx=a[l++];
}
else num1=b[r],maxn=a[r--],minx=a[l++];
tot-=2;
if(!tot)
{
if(op%2==1) cnt--;
break;
}
if(head>=tail)
{
if(q[head].val<a[l]||(q[head].val==a[l]&&q[head].id<b[l]))
num2=q[head].id,minxx=q[head].val;
else num2=b[l],minxx=a[l];
}
else num2=b[l],minxx=a[l];
if(maxn-minx>minxx||(maxn-minx==minxx&&num1>num2))
{
if(op%2==1) cnt--;
break;
}
op++,tot++;
a[--l]=maxn-minx,b[l]=num1;
}
printf("%d\n",cnt+2);
break;
}
else if(maxn-minx>a[r]||(maxn-minx==a[r]&&num1>b[r]))
{
a[++r]=maxn-minx;
b[r]=num1;
}
else q[++head].val=maxn-minx,q[head].id=num1;
cnt++;
}
}
return 0;
}
考场上好像特判都写挂了,我不是人/kk。