2022.11.03 NOIP2022 模拟赛二

绯色 IOI(开端)

之前做过了,见杂题题解(一),话说这个系列是不是好久没更新了。

Code
const int N=2e5+5;
int n,m,a[N];
int main() {
    n=read();
    FOR(i,1,n) a[i]=read();
    sort(a+1,a+n+1);
    ll ans=0;
    for(int j=1;j<n-1;j++) ans+=1ll*(a[j]-a[j+2])*(a[j]-a[j+2]);
    ans+=1ll*(a[1]-a[2])*(a[1]-a[2])+1ll*(a[n]-a[n-1])*(a[n]-a[n-1]);
    printf("%lld\n",ans);
}

绯色 IOI(抵达)

首先先用一个类似 bfs 的过程求出每一个点的庇护所是哪一个点,具体使用 set 稍微维护一下就行。

根据庇护所的关系,可以得到若干个危险指数间的大小关系,将这些关系建边,用堆拓扑排序即可。

Code
const int N=5e5+5;
vi G[N],D[N];
set<int> f[N];
int p[N],don[N],deg[N],a[N],cnt;
int main() {
	int n;scanf("%d",&n);
	if(n%2==1) return puts("-1"),0;
	FOR(i,1,n-1) {
		int x,y;scanf("%d %d",&x,&y);
		G[x].pb(y),G[y].pb(x);
	}
	FOR(u,1,n) for(int v:G[u]) f[u].insert(v);
	queue<int> q1;
	FOR(u,1,n) if(sz(f[u])==1) don[u]=1,q1.push(u);
	while(sz(q1)) {
		int u=q1.front();q1.pop();
		p[u]=*f[u].begin();
		for(int v:G[p[u]]) {
			f[v].erase(p[u]);
			if(don[v]&&!p[v]) return puts("-1"),0;
		}
		for(int v:G[p[u]]) if(sz(f[v])==1) don[v]=1,q1.push(v);
	}
	FOR(u,1,n) for(int v:G[u]) if(v!=p[u]) D[p[u]].pb(v),deg[v]++;
	priority_queue<int,vector<int>,greater<int> > dq;
	FOR(i,1,n) if(deg[i]==0) dq.push(i);
	while(sz(dq)) {
		int u=dq.top();dq.pop();
		a[++cnt]=u;
		for(int v:D[u]) if(--deg[v]==0) dq.push(v);
	}
	if(cnt!=n) return puts("-1"),0;
	FOR(i,1,n) printf("%d ",a[i]);
	puts("");
} 

绯色 IOI(危机)

因为转移代价没有性质,大胆猜测暴力建出 DAG 复杂度是对的。

事实上确实是这样,我们求出每个点可以间接或直接炸到哪里,建出 DAG 拓扑排序 DP 即可。

代码中使用了单调栈来求出区间。

Code
const int N=3e5+5,mod=998244353;
struct bomb {ll x,r;int v,id;} b[N];
bool cmp(bomb a,bomb b) {return a.x<b.x;}
int val[N],deg[N],rid[N],l[N],r[N],st[N],top;
ll f[N],x[N],R[N];
vi G[N];
int main() {
    int n;scanf("%d",&n);
    FOR(i,1,n) scanf("%lld",&b[i].x),b[i].id=i;
    FOR(i,1,n) scanf("%lld",&b[i].r);
    FOR(i,1,n) scanf("%d",&b[i].v);
    sort(b+1,b+n+1,cmp);
    FOR(i,1,n) x[i]=b[i].x,R[i]=b[i].r,val[i]=b[i].v,rid[b[i].id]=i;
    FOR(i,1,n) {
        l[i]=lower_bound(x+1,x+n+1,x[i]-R[i])-x;
        r[i]=upper_bound(x+1,x+n+1,x[i]+R[i])-x-1;
    }
    FOR(i,1,n) {
        while(top&&st[top]>=l[i]) chkmin(l[i],l[st[top]]),top--;
        st[++top]=i;
    }
    top=0;
    ROF(i,n,1) {
        while(top&&st[top]<=r[i]) chkmax(r[i],r[st[top]]),top--;
        st[++top]=i;
    }
    FOR(i,1,n) FOR(j,l[i],r[i]) if(j!=i) G[i].pb(j),deg[j]++;
    queue<int> q;
    FOR(i,1,n) if(deg[i]==0) q.push(i);
    while(sz(q)) {
        int u=q.front();q.pop();
        for(int v:G[u]) {
            f[v]=max(f[v],f[u]+(1ll*val[u]*val[v]%mod+(val[u]^val[v])%mod)%mod);
            if(--deg[v]==0) q.push(v);
        }
    }
    FOR(i,1,n) printf("%lld\n",f[rid[i]]);
}

绯色 IOI(悬念)

我们将问题稍微转化一波,考虑对于两条从左部点 \(a_i\) 出发的边,设他们连向 \(b_i,b_j\),改为将 \(b_i,b_j\) 连一条无向边,要求将所有的无向边定向,使得每个点入度为 \(1\) 并且对应的权最大。

因为左侧满匹配,利用 Hall 定理得到,在左侧的任意一个 \(k\) 的点集,往右边连到的点集大于等于左边点的个数,左边是边而右边是点,故图的任意一个连通块为基环树或树。

基环树是简单的,因为每个点入度为 \(1\),故树边一定是叶向的,而环边要么是顺时针要么是逆时针的,这些都很好维护。

树的话考虑定根,定成叶向树,维护每个点的答案,若一条边定成 \(u\to v\),则根不在 \(v\) 的子树内会有贡献,否则则根在 \(v\) 的子树内会有贡献。

区间加维护区间 \(\max\) 即可。

Code
const int N=5e5+5;
int n,m,T;
int a[N],b[N],dfn[N],re[N],dfc,rt[N],pre[N],dep[N];
int head[N],son[N*2],nxt[N*2],tot,rev[N*2],val[N*2],typ[N*2],sum[N][3],fl[N];
vi edg;
int ans,cur;
void add(int x,int y) {son[++tot]=y,nxt[tot]=head[x],head[x]=tot;}
void dfs(int u) {
    for(int i=head[u],v;i;i=nxt[i]) {
        if(!dep[v=son[i]]) pre[v]=i,dep[v]=dep[u]+1,dfs(v);
        else if(dep[v]>=dep[u]) {
            int j=pre[v];
            while(j&&son[j]!=u) edg.pb(j),j=pre[son[rev[j]]];
            edg.pb(rev[i]);
        }
    }
}
void dfs2(int u) {
    dfn[u]=++dfc,rt[u]=cur;
    for(int i=head[u],v;i;i=nxt[i]) if(!dfn[v=son[i]]) dfs2(v);
    re[u]=dfc;
}
int ma[N*4],laz[N*4];
void pushadd(int p,int v) {ma[p]+=v,laz[p]+=v;}
void pushdown(int p) {
    if(laz[p]) {
        pushadd(p*2,laz[p]),pushadd(p*2+1,laz[p]);
        laz[p]=0;
    }
}
void pushup(int p) {ma[p]=max(ma[p*2],ma[p*2+1]);}
void change(int p,int l,int r,int x,int y,int v) {
    if(x>y||y<l||x>r) return;
    if(x<=l&&r<=y) return pushadd(p,v);
    int mid=(l+r)>>1;
    pushdown(p);
    if(x<=mid) change(p*2,l,mid,x,y,v);
    if(y>mid) change(p*2+1,mid+1,r,x,y,v);
    pushup(p);
}
int query(int p,int l,int r,int x,int y) {
    if(x>y||y<l||x>r) return 0;
    if(x<=l&&r<=y) return ma[p];
    int mid=(l+r)>>1,m=0;
    pushdown(p);
    if(x<=mid) chkmax(m,query(p*2,l,mid,x,y));
    if(y>mid) chkmax(m,query(p*2+1,mid+1,r,x,y));
    return m;
}
void change(int x,int v) {
    int dt=v-val[x],R=rt[son[x]];
    val[x]=v;
    if(fl[R]) {
        ans-=sum[R][0]+max(sum[R][1],sum[R][2]);
        if(typ[x]||dfn[son[x]]>dfn[son[rev[x]]])
            sum[R][typ[x]]+=dt;
        ans+=sum[R][0]+max(sum[R][1],sum[R][2]);
    }
    else {
        ans-=query(1,1,n,dfn[R],re[R]);
        if(dfn[son[x]]>dfn[son[rev[x]]])
            change(1,1,n,dfn[R],dfn[son[x]]-1,dt),
            change(1,1,n,re[son[x]]+1,re[R],dt);
        else 
            change(1,1,n,dfn[son[rev[x]]],re[son[rev[x]]],dt);
        ans+=query(1,1,n,dfn[R],re[R]);
    }
}
int main() {
    m=read(),n=read(),T=read();
    FOR(i,1,m) a[i]=read();
    FOR(i,1,m) b[i]=read();
    FOR(i,1,m) {
        int x=(a[i]-b[i]+n)%n,y=(a[i]+b[i])%n;
        if(x>y) swap(x,y);
        x++,y++,add(y,x);
        if(x!=y) rev[tot]=tot+1,add(x,y),rev[tot]=tot-1;
        else rev[tot]=tot;
    }
    FOR(i,1,n) if(!rt[i]) {
        edg.clear(),cur=i,dep[i]=1;
        dfs(i),fl[i]=(sz(edg)>0);
        if(fl[i]) {
            for(int j:edg) typ[j]=1,typ[rev[j]]=2;
            dfs2(son[edg[0]]);
        }
        else dfs2(i);
    }
    FOR(i,1,tot) change(i,read());
    printf("%d\n",ans);
    int q=read();
    FOR(i,1,q) {
        int x=read()-T*ans,y=read()-T*ans;
        change(x,y),printf("%d\n",ans);
    }
}
posted @ 2022-11-03 16:32  cnyz  阅读(79)  评论(0编辑  收藏  举报