正题
1个半小时就可以AK的题目
A
发现一个点只会被吸一次,那么就是找对应区间,双指针即可,求答案的时候注意一下两边有0的情况。
#include<bits/stdc++.h>
using namespace std;
const int N=100010;
int T,n;
int a[N],b[N];
void read(int&x){
char ch=getchar();x=0;
while(ch<'0' || ch>'9') ch=getchar();
while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar();
}
int main(){
read(T);
while(T--){
read(n);
long long t1=0,t2=0;
for(int i=1;i<=n;i++) read(a[i]),t1+=a[i];
for(int i=1;i<=n;i++) read(b[i]),t2+=b[i];
if(t1!=t2){printf("-1\n");continue;}
int pos1=1,pos2=1,tot=0,ans=0;
bool tf=true;
while(pos1<=n && pos2<=n){
tot=0;
bool we=false;
while(tot<b[pos2]){
tot+=a[pos1];
if(pos1!=pos2 && a[pos1] && !we) we=true,ans++;pos1++;
if(tot==b[pos2]) break;
else if(tot>b[pos2]) {tf=false;break;}
}
if(!tf) break;
pos2++;
}
if(!tf) printf("-1\n");
else printf("%d\n",ans);
}
}
B
最小生成树即可,容易证明一条新加进来的边若联通两个已经联通的点,那么肯定没有前面所有加起来优。
#include<bits/stdc++.h>
using namespace std;
const int N=300010;
struct edge{
int y,next,c;
}s[N<<1];
int n,m,q;
int first[N],len=0;
int fa[N],dep[N],f[N][20];
long long dis[N];
const long long mod=998244353;
int findpa(int x){return fa[x]!=x?fa[x]=findpa(fa[x]):x;}
void ins(int x,int y,int c){
s[++len]=(edge){y,first[x],c};first[x]=len;
s[++len]=(edge){x,first[y],c};first[y]=len;
}
void dfs(int x,int fa){
for(int i=first[x];i!=0;i=s[i].next) if(s[i].y!=fa){
int y=s[i].y;
dep[y]=dep[x]+1;f[y][0]=x;dis[y]=dis[x]+s[i].c;
for(int k=1;k<=19;k++) f[y][k]=f[f[y][k-1]][k-1];
dfs(y,x);
}
}
int lca(int x,int y){
if(dep[x]>dep[y]) swap(x,y);
for(int k=19;k>=0;k--) if(dep[f[y][k]]>=dep[x]) y=f[y][k];
if(x==y) return x;
for(int k=19;k>=0;k--) if(f[x][k]!=f[y][k]) x=f[x][k],y=f[y][k];
return f[x][0];
}
int main(){
scanf("%d %d",&n,&m);
int x,y,c=1;
for(int i=1;i<=n;i++) fa[i]=i;
for(int i=1;i<=m;i++){
scanf("%d %d",&x,&y),c=c*2%mod;
int fx=findpa(x),fy=findpa(y);
if(fx!=fy) ins(x,y,c),fa[fx]=fy;
}
dep[1]=1,dfs(1,0);
scanf("%d",&q);
while(q--){
scanf("%d %d",&x,&y);
printf("%lld\n",(dis[x]+dis[y]-2*dis[lca(x,y)])%mod);
}
}
C
把ai从大到小排序之后,把式子写出来:
那么直接维护一个线段树或者树状数组就可以了。
#include<bits/stdc++.h>
using namespace std;
const int N=100010;
int n,m,rt,num;
int a[N];
int lazy[10000010],sum[10000010],t[10000010],ls[10000010],rs[10000010];
int ci[N];
const long long mod=998244353,data=249561089,fuck=332748119;
void pushdown(int now){
if(lazy[now]!=1){
if(ls[now]) lazy[ls[now]]=1ll*lazy[ls[now]]*lazy[now]%mod,sum[ls[now]]=1ll*sum[ls[now]]*lazy[now]%mod;
if(rs[now]) lazy[rs[now]]=1ll*lazy[rs[now]]*lazy[now]%mod,sum[rs[now]]=1ll*sum[rs[now]]*lazy[now]%mod;
lazy[now]=1;
}
}
void times(int now,int x,int y,int c,int l,int r){
if(!now) return;
if(x==l && y==r){
lazy[now]=1ll*lazy[now]*c%mod;
sum[now]=1ll*sum[now]*c%mod;
return ;
}
pushdown(now);
int mid=(l+r)/2;
if(y<=mid) times(ls[now],x,y,c,l,mid);
else if(mid<x) times(rs[now],x,y,c,mid+1,r);
else times(ls[now],x,mid,c,l,mid),times(rs[now],mid+1,y,c,mid+1,r);
sum[now]=sum[ls[now]]+sum[rs[now]];
sum[now]>=mod?sum[now]-=mod:0;
}
void add(int&now,int x,int c,int op,int l,int r){
if(!now) now=++num,lazy[now]=1;
sum[now]+=c;sum[now]>=mod?sum[now]-=mod:0;t[now]+=op;
if(l==r) return ;
pushdown(now);
int mid=(l+r)/2;
if(x<=mid) add(ls[now],x,c,op,l,mid);
else add(rs[now],x,c,op,mid+1,r);
}
int get_tot(int now,int x,int y,int l,int r){
if(!now) return 0;
if(x==l && y==r) return t[now];
pushdown(now);
int mid=(l+r)/2;
if(y<=mid) return get_tot(ls[now],x,y,l,mid);
else if(mid<x) return get_tot(rs[now],x,y,mid+1,r);
else return get_tot(ls[now],x,mid,l,mid)+get_tot(rs[now],mid+1,y,mid+1,r);
}
int get_sum(int now,int x,int y,int l,int r){
if(!now) return 0;
if(x==l && y==r) return sum[now];
pushdown(now);
int mid=(l+r)/2;
if(y<=mid) return get_sum(ls[now],x,y,l,mid);
else if(mid<x) return get_sum(rs[now],x,y,mid+1,r);
else return (get_sum(ls[now],x,mid,l,mid)+get_sum(rs[now],mid+1,y,mid+1,r))%mod;
}
int main(){
scanf("%d",&n);
ci[0]=1;for(int i=1;i<=n;i++) ci[i]=1ll*ci[i-1]*data%mod;
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
times(rt,0,a[i],data,0,998244352);
add(rt,a[i],1ll*a[i]*ci[get_tot(rt,a[i]+1,998244352,0,998244352)+1]%mod,1,0,998244352);
}
int x,y;
scanf("%d",&m);
printf("%lld\n",1ll*sum[1]*332748118%mod);
for(int i=1;i<=m;i++){
scanf("%d %d",&x,&y);
if(y<a[x]){
if(y+1<=a[x]-1) times(rt,y+1,a[x]-1,fuck,0,998244352);
add(rt,a[x],mod-1ll*a[x]*ci[get_tot(rt,a[x],998244352,0,998244352)]%mod,-1,0,998244352);
add(rt,y,1ll*y*ci[get_tot(rt,y+1,998244352,0,998244352)+1]%mod,1,0,998244352);
}
else if(a[x]<y){
if(a[x]+1<=y-1) times(rt,a[x]+1,y-1,data,0,998244352);
add(rt,a[x],mod-1ll*a[x]*ci[get_tot(rt,a[x]+1,998244352,0,998244352)+1]%mod,-1,0,998244352);
add(rt,y,1ll*y*ci[get_tot(rt,y,998244352,0,998244352)+1]%mod,1,0,998244352);
}
a[x]=y;
printf("%lld\n",1ll*sum[1]*332748118%mod);
}
}
D
n-1个的矩形交,发现n个矩形交会被算n次,减掉就可以了,其他用前缀后缀来维护。
#include<bits/stdc++.h>
using namespace std;
const int N=200010;
int n,r,c;
long long tot=0,t=0;
int xx1[N],xx2[N],yy1[N],yy2[N];
int px1[N],px2[N],py1[N],py2[N];
int lx1[N],lx2[N],ly1[N],ly2[N];
int main(){
scanf("%d %d %d",&n,&r,&c);
for(int i=1;i<=n;i++) scanf("%d %d %d %d",&xx1[i],&yy1[i],&xx2[i],&yy2[i]);
px2[0]=py2[0]=1e9;lx2[n+1]=ly2[n+1]=1e9;
for(int i=1;i<=n;i++) px1[i]=max(px1[i-1],xx1[i]),py1[i]=max(py1[i-1],yy1[i]),px2[i]=min(px2[i-1],xx2[i]),py2[i]=min(py2[i-1],yy2[i]);
for(int i=n;i>=1;i--) lx1[i]=max(lx1[i+1],xx1[i]),ly1[i]=max(ly1[i+1],yy1[i]),lx2[i]=min(lx2[i+1],xx2[i]),ly2[i]=min(ly2[i+1],yy2[i]);
if(px1[n]<=px2[n] && py1[n]<=py2[n]) t=1ll*(px2[n]-px1[n]+1)*(py2[n]-py1[n]+1);tot=t;
for(int i=1;i<=n;i++){
int xx1=max(px1[i-1],lx1[i+1]),xx2=min(px2[i-1],lx2[i+1]),yy1=max(py1[i-1],ly1[i+1]),yy2=min(py2[i-1],ly2[i+1]);
if(xx2>=xx1 && yy2>=yy1) tot+=1ll*(xx2-xx1+1)*(yy2-yy1+1);
tot-=t;
}
printf("%lld\n",tot);
}
E
是一道式子比较恶心的优化Dp,笔者在考试的时候写了很久拿到了68分,题解已经很详细,在这里不再赘述。
#include<bits/stdc++.h>
using namespace std;
const int N=2010;
int T,n;
int d[N],p[N];
int f[N][N<<1];
const int mod=998244353;
int main(){
scanf("%d",&T);
while(T--){
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&d[i]);
bool tf=true;
for(int i=1;i<=n-1;i++) scanf("%d",&p[i]);p[n]=0;
for(int i=1;i<=n-1;i++) if((p[i]+d[i+1]-p[i+1])&1) tf=false;
if(!tf){
printf("0\n");
continue;
}
for(int i=1;i<=n;i++) for(int j=0;j<=p[i];j++) f[i][j]=0;
for(int i=0;i<=min(d[1],p[1]);i++) f[1][i]=1;
for(int i=1;i<n;i++){
for(int j=0;j<=p[i];j++)
for(int q=0;q<=d[i+1];q++)
if(max((p[i]+d[i+1]-p[i+1])/2-min(d[i+1]-q,p[i]-j),0)<=min(min(q,j),(p[i]+d[i+1]-p[i+1])/2)){
(f[i+1][j+q-2*min(min(q,j),(p[i]+d[i+1]-p[i+1])/2)]+=f[i][j])%=mod;
(f[i+1][j+q-2*max((p[i]+d[i+1]-p[i+1])/2-min(d[i+1]-q,p[i]-j),0)+2]+=mod-f[i][j])%=mod;
}
for(int j=2;j<=p[i+1];j+=2) (f[i+1][j]+=f[i+1][j-2])%=mod;
for(int j=3;j<=p[i+1];j+=2) (f[i+1][j]+=f[i+1][j-2])%=mod;
}
printf("%d\n",f[n][0]);
}
}
F
这题怎么做呢?
首先转化为一棵有根树,这样比较好算贡献(我这都没想到)。
接着对于一个<=D的点,暴力即可,否则对儿子的权值维护一个高维前缀和,发现答案就是该点权值取反的子集和。
但是这样每次修改维护高维前缀和的时间都是2^m的,发现查询居然只要O(1),想办法均摊。
我们在维护高维前缀和的时候只把一个权值加到前10位超集上面,然后询问的时候查询后10位的子集和,想一想就知道一对子集对(x,y)会在y前10位+x后10位的这个点被算1遍。至于D的取值,1000即可,因为你会发现再小空间开不下。
我认为这次比赛严格符合NOIP2018的整体难度,思维清晰,较为自然的题目背景,恰当合适的数据范围,再加上提高区分度的多组数据测试模式,真是一套值得赞叹的好题!
本文解析了NOIP2018竞赛中的六道题目,包括双指针算法、最小生成树、线段树、矩形交集、优化DP及高维前缀和等高级算法的应用。每道题目的解析深入浅出,旨在帮助读者理解并掌握算法背后的逻辑。
179

被折叠的 条评论
为什么被折叠?



