2023.9.6 wzOI全场题解
A
说实话,这道题耗了我挺久的,很烦人。
原题:P7175 [COCI2014-2015#4] PŠENICA
如果你一个一个模拟的删,最后一个一个地都到中间去了,复杂度是
注意到,Mirko 的操作是让最小的变第二小,Slavko 是让最大的变第二大,也就是在数量小于
具体细节不妨看代码:
Code
int n,a[200005],cnt,trans[200005],b[200005];
int main(){
n=read();
for(int i=1;i<=n;i++)++a[read()];
for(int i=0;i<=200000;i++){
if(a[i])b[++cnt]=a[i],trans[cnt]=i;//记录
}
int l=1,r=cnt;
while(r-l>=3){//如果个数大于4就一直进行减少操作
if(b[l]>b[r])b[r-1]+=b[r],b[l+1]+=b[r],b[l]-=b[r],b[r]=0;//减去最小的
else b[r-1]+=b[l],b[l+1]+=b[l],b[r]-=b[l],b[l]=0;
if(!b[l])l++;//如果没了就移动指针
if(!b[r])r--;
}
if(r-l<2)printf("Slavko\n%d %d",trans[l],trans[r]);//这里不仅是考虑了一开始长度就小于3的清况
//还有因为长度等于四而两边相等然后减掉后只剩下两个的情况
//所以答案显然是 Slavko 胜利
else{//长度为 3 时
if(b[l]<=b[r])puts("Mirko"),l++;//取等时因为Mirko是先手,所以胜利
else puts("Slavko"),r--;
printf("%d %d",trans[l],trans[r]);
}
}
B
不会,但是之后听懂了,有时间我自己手推一遍,数学公式打起来很麻烦,所以直接复制 fjp 的源码了。
前言
这是前省一选手在看到数竞题之后有感而发,而出的一道题,他用心良苦,只为锻炼信竞选手的数学能力
题意
有
- 位置相邻
- 编号之差的绝对值不超过
3
可以任意交换,问最终可以得到多少种不同的排列。
数竞原题是这样,然后我们把
题目分析 by\ fjp
记数组
首先,第一个位置只有可能是
接下来考虑
首先,
接着,考虑
综上,
然后,考虑
先把简单的情况处理掉。
最后,我们考虑
其中,
#include <bits/stdc++.h>
using namespace std;
template <typename T>
bool read_(T &ready_get)
{
T x=0,f=1;
char ch=getchar();
while(!('0'<=ch&&ch<='9'))
{
if(ch=='-') f=-1;
ch=getchar();
if(ch==-1) return false;
}
while('0'<=ch&&ch<='9')
{
x=x*10+(ch-'0');
ch=getchar();
}
ready_get=x*f;
return true;
}
template <typename T>
void write_(T ready_put)
{
if(ready_put<0) putchar('-'),ready_put*=-1;
if(ready_put>9) write_(ready_put/10);
putchar(ready_put%10+'0');
}
int T,n;
const long long mod=998244353,rn=2e5+10;
long long x[rn],A[rn],B[rn],C[rn],D[rn];
void init()
{
A[0]=0;B[0]=0;C[0]=0;D[0]=0;x[0]=1;
A[1]=1;B[1]=0;C[1]=0;D[1]=0;x[1]=1;
A[2]=1;B[2]=1;C[2]=0;D[2]=0;x[2]=2;
A[3]=2;B[3]=2;C[3]=2;D[3]=0;x[3]=6;
A[4]=6;B[4]=6;C[4]=6;D[4]=6;x[4]=24;
for(int i=5;i<=rn;++i)
{
A[i]=x[i-1]%mod;
B[i]=(B[i-2]+x[i-2]+x[i-3]+2*x[i-4])%mod;
C[i]=(B[i-1]+x[i-3]+3*x[i-4]+x[i-5])%mod;
D[i]=(B[i-2]+C[i-1]+3*x[i-4]+x[i-5])%mod;
x[i]=(A[i]+B[i]+C[i]+D[i])%mod;
}
}
int main()
{
init();
read_(T);
while(T--)
{
read_(n);
write_(x[n]),putchar('\n');
}
return 0;
}
C
原题:P4042 [AHOI2014/JSOI2014] 骑士游戏
前言
应该是第一次考场切紫(之前模拟赛没评级我也不知道有没有切过紫的),虽然 CE 了但是我还是觉得是考场切紫。说实话我感觉这题比最后两题都要简单,但是后两题都是蓝,就一个这个是紫的,洛谷评级这么合理?
\color{Red}{千万不要在考场上用一些没有用过常见英文单词做变量名或者函数名!!}
血的教训,因为有一个数组用了 kill[],虽然本地可以编译并且所有样例都过了,但是在各大 OJ 上都是 CE。所以为什么不报错呢?真的服了。100->0。
思路
一开始觉得应该是跑个图什么的,比如用拓扑去更新之类的,发现有环,想到是不是要缩点,想到自己 tarjan 已经忘干净了,然后先去看了一下后面的题发现都不是什么善茬,又回来了。
然后就有了一个很关键的贪心思路,千万要注意,普通攻击是杀不死怪物的(
那很显然的,激光攻击最小的那只就一定会被激光杀死,此时我们完全不需要考虑任何的环之类的,继续考虑一个类拓扑的东西。
如果
然后这题又做完了,复杂度是一个非常优秀的
Code
typedef long long ll;
const ll N=200005;
typedef pair<ll,ll> PII;
ll n,inn[N];//入度
vector<ll>f[N];
bool die[N];//这只怪物死了没
ll to[N],kil[N];//to 表示分裂代价,kil表示杀死代价
priority_queue<PII,vector<PII>,greater<PII> >Q;
int main(){
n=read();
for(ll i=1;i<=n;i++){
to[i]=read();
kil[i]=read();//被杀死的代价
Q.push({kil[i],i});
inn[i]=read();//入度其实就是可以变成的怪物数
for(ll j=1;j<=inn[i];j++)
f[read()].push_back(i);
}
while(!Q.empty()){
ll w=Q.top().first,x=Q.top().second;
Q.pop();
if(die[x])continue;
die[x]=1;
kil[x]=min(kil[x],w);
for(ll y:f[x]){
if(die[y])continue;
to[y]+=w;//因为这只怪物必死,所以分裂代价就要加
if(!--inn[y])Q.push({to[y],y});//如果不能分裂,说明分裂可以直接死,加入答案
}
}
cout<<kil[1];//输出杀死1的代价即可。
return 0;
}
后记
我觉得 SPFA 的复杂度不一定正确,思路也很巧妙,但是显然可以用类似于网络图套菊花图之类的特殊图卡掉,这题数据水,不然看 D 题虽然在赛时过了但是却显然过不了 CF。
D
原题:CF1627E Not Escaping
考场上想不到什么很好的做法,只是觉得这个看上去很好建图,一看是单向边且只能向上,有想到 dp,但是觉得状态太难记录了,好像不是按照层数来的,比如如果去掉
每个楼梯开个点,然后跑个 SPFA 即可。
赛时满分 Code
const int N=100050;
typedef pair<int,ll> PII;
struct FLOOR{int stx,sty,enx,eny;ll hp;};
struct loca{int x,y;}w[N];
int n,m,g,a[N],tot;
vector<FLOOR>fl[N];
vector<PII>f[N],id;
queue<int>Q;
ll dis[N];
bool vis[N];
int main(){
for(int T=read();T--;){
n=read(),m=read(),g=read();
for(int i=1;i<=n;i++){
a[i]=read();
}
for(int i=1;i<=g;i++){
int x1=read(),y1=read(),x2=read(),y2=read(),hp=read();
fl[x1].push_back({x1,y1,x2,y2,hp});
}
w[(tot=1)]={1,1};
for(int i=1;i<=tot;i++){
int F=w[i].x;
for(auto flo:fl[F]){
w[++tot]={flo.enx,flo.eny};
if(flo.enx==n)id.push_back({tot,flo.eny});
f[i].push_back({tot,1ll*abs(flo.sty-w[i].y)*a[F]-flo.hp});
}
}
for(int i=2;i<=tot;i++)dis[i]=1e18;
dis[1]=0;
Q.push(1);
while(!Q.empty()){
int x=Q.front();
Q.pop();
vis[x]=0;
for(auto PI:f[x]){
ll y=PI.first,w=PI.second;
if(dis[y]>dis[x]+w){
dis[y]=dis[x]+w;
if(!vis[y]){
vis[y]=1;
Q.push(y);
}
}
}
}
ll ans=1e18;
for(auto PI:id){
int y=PI.first,eny=PI.second;
if(dis[y]+1ll*abs(m-eny)*a[n]<ans)ans=dis[y]+1ll*abs(m-eny)*a[n];
}
if(ans>1e17)puts("NO ESCAPE");
else printf("%lld\n",ans);
for(int i=1;i<=tot;i++)f[i].clear(),vis[i]=0;
for(int i=1;i<=n;i++)fl[i].clear();
id.clear();
}
return 0;
}
然而不出我所料,在 Codeforces 上第四个点就 T 了,原因之一是其本身就是一个网络图,然后显然就噶了。
考虑正解。
SPFA 被卡的原因大概也有我对于每一个终点都通过起点跟起点所在层的所有终点连了边,而如果同一层有
首先还是用一个很显然的结论,我只可能往
Code
const int N=100050;
typedef pair<int,ll> PII;
struct FLOOR{
int i,x,to=-1,hp;
inline bool operator <(const FLOOR &w)const{return x<w.x;}
};
int n,m,g,a[N],tot;
vector<FLOOR>f[N];
ll dp[200005];
int main(){
for(int T=read();T--;tot=0){
n=read(),m=read(),g=read();
for(int i=1;i<=n;i++)
a[i]=read(),f[i].clear();
for(int i=1;i<=g;i++){
int x1=read(),y1=read(),x2=read(),y2=read(),hp=read();
f[x1].push_back({++tot,y1,tot+1,hp});
f[x2].push_back({++tot,y2,-1,0});
}
f[1].push_back({++tot,1,-1,0});
f[n].push_back({++tot,m,-1,0});
for(int i=1;i<=tot;i++)dp[i]=1e18+0.3;
dp[tot-1]=0;
for(int t=1;t<=n;t++){
sort(f[t].begin(),f[t].end());
for(int i=1;i<f[t].size();i++)
dp[f[t][i].i]=min(dp[f[t][i].i],dp[f[t][i-1].i]+1ll*(f[t][i].x-f[t][i-1].x)*a[t]);
for(int i=f[t].size()-2;i>=0;--i)
dp[f[t][i].i]=min(dp[f[t][i].i],dp[f[t][i+1].i]+1ll*(f[t][i+1].x-f[t][i].x)*a[t]);
for(int i=0;i<f[t].size();i++)
if(dp[f[t][i].i]<1e18)
dp[f[t][i].to]=min(dp[f[t][i].to],dp[f[t][i].i]-f[t][i].hp);
}
if(dp[tot]<1e18)printf("%lld\n",dp[tot]);
else puts("NO ESCAPE");
}
return 0;
}
E
原题:P7497 四方喝彩
第五的题唉!咳咳。赛时没啥思路,打了个暴力跑了。
发现没有 3 操作就是个线段树2模板,有的话怎么思考呢?
我一开始有想比如对每个区间记一个封印时长,然而这样在对应时间的时候可能因为上面标记没有下来导致没有被解封,如果强行下传解封可能导致复杂度退化到
封印的问题解决了,答案怎么计算?我们每次乘法和加分肯定是依据没被封印的长度来的,不如我们记三个值:没封印的值,封印的值,没封印的个数。然后具体细节看代码吧。
Code
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define mid (l+r>>1)
#define ls (root<<1)
#define rs (root<<1|1)
#define lson ls,l,mid
#define rson rs,mid+1,r
const int mod=1e9+7;
int read(){
int x=0,f=1;char c=getchar();
for(;!isdigit(c);c=getchar())if(c=='-')f=-1;
for(;isdigit(c);c=getchar())x=(x<<3)+(x<<1)+(c^48);
return x*f;
}
struct tree{
ll zv,fv,len,mu=1,plus,ban;
}tr[800005];
int n,m;
inline void pushup(int root){
tr[root].fv=(tr[ls].fv+tr[rs].fv)%mod;
tr[root].zv=(tr[ls].zv+tr[rs].zv)%mod;
tr[root].len=tr[ls].len+tr[rs].len;
}
inline void pushdown(int root){
if(!tr[root].plus&&tr[root].mu==1)return;
if(!tr[ls].ban){
(tr[ls].zv*=tr[root].mu)%=mod;
(tr[ls].zv+=tr[root].plus*tr[ls].len%mod)%=mod;
tr[ls].plus=(tr[ls].plus*tr[root].mu%mod+tr[root].plus)%mod;
(tr[ls].mu*=tr[root].mu)%=mod;
}
if(!tr[rs].ban){
(tr[rs].zv*=tr[root].mu)%=mod;
(tr[rs].zv+=tr[root].plus*tr[rs].len%mod)%=mod;
tr[rs].plus=(tr[rs].plus*tr[root].mu+tr[root].plus)%mod;
(tr[rs].mu*=tr[root].mu)%=mod;
}
tr[root].plus=0;
tr[root].mu=1;
return;
}
void build(int root,int l,int r){
if(l==r)return tr[root].len=1,tr[root].zv=read(),void();
build(lson),build(rson);
pushup(root);
}
void Mu(int root,int l,int r,int x,int y,ll v){
if(tr[root].ban)return;
if(x<=l&&r<=y){
(tr[root].zv*=v)%=mod;
(tr[root].mu*=v)%=mod;
(tr[root].plus*=v)%=mod;
return;
}
pushdown(root);
if(mid>=x)Mu(lson,x,y,v);
if(mid<y)Mu(rson,x,y,v);
pushup(root);
}
void add(int root,int l,int r,int x,int y,ll v){
if(tr[root].ban)return;
if(x<=l&&r<=y){
(tr[root].zv+=v*tr[root].len%mod)%=mod;
(tr[root].plus+=v)%=mod;
return;
}
pushdown(root);
if(mid>=x)add(lson,x,y,v);
if(mid<y)add(rson,x,y,v);
pushup(root);
}
void locked(int root,int l,int r,int x,int y){
if(x<=l&&r<=y){
if(!tr[root].ban){
tr[root].len=0;
(tr[root].fv+=tr[root].zv)%=mod;
tr[root].zv=0;
}
tr[root].ban++;
return;
}
pushdown(root);
if(mid>=x)locked(lson,x,y);
if(mid<y)locked(rson,x,y);
if(!tr[root].ban)pushup(root);
}
void unlocked(int root,int l,int r,int x,int y){
if(x<=l&&r<=y){
if(!--tr[root].ban){
if(l==r)
(tr[root].zv+=tr[root].fv)%=mod,tr[root].fv=0,tr[root].len=1;
else pushdown(root),pushup(root);
}
return;
}
pushdown(root);
if(mid>=x)unlocked(lson,x,y);
if(mid<y)unlocked(rson,x,y);
if(!tr[root].ban)pushup(root);
}
ll query(int root,int l,int r,int x,int y){
if(x<=l&&r<=y)return (tr[root].zv+tr[root].fv)%mod;
pushdown(root);
ll ans=0;
if(mid>=x)ans+=query(lson,x,y);
if(mid<y)ans+=query(rson,x,y);
if(!tr[root].ban)pushup(root);
return ans%mod;
}
vector<pair<int,int> >unlocq[200005];
int main(){
n=read(),m=read();
build(1,1,n);
for(int t=1;t<=m;t++){
int opt=read(),l=read(),r=read();
if(opt==4){
printf("%lld\n",query(1,1,n,l,r));
}else{
ll v=read();
if(opt==1)add(1,1,n,l,r,v);
else if(opt==2)Mu(1,1,n,l,r,v);
else{
locked(1,1,n,l,r);
unlocq[t+v].push_back({l,r});
}
}
for(auto PI:unlocq[t])
unlocked(1,1,n,PI.first,PI.second);
}
return 0;
}
后记
注意了!!
- 每次操作一定要执行解封操作,不要中途给它 continue 了。
- 结构体中不要每个值都赋初值,这样会导致编译速度和编译文件大大增加,可能在洛谷上莫名其妙 CE。具体看博客洛谷编译时间太长会导致编译失败?