正睿csp-s 7连测 day 7

nagato--yuki / 2025-01-25 / 原文

总结

由于晚上六点尚处于机房的打摆时间,所以先颓了三十分钟。
\(5\) 分钟写完 t1,继续摆到七点。
t2 想了一会,一开始以为是先贪心 + dp,发现被样例卡了。然后再想了一个 dp + 贪心,过了大样例。好像过了?
t3 想了半小时,好像是线段树,但一时不知道维护什么。先写了一个 \(60\) 分暴力。
t4 看了一眼瞬间没有写的欲望。
结果:100+70+60+0
上了点分,开心。

解析

A. 庄园(manor)

难度:橙
从左到右枚举即可。


#include<bits/stdc++.h>
#define ll long long
#define mxn 5010
using namespace std;
ll n,q,l[mxn],r[mxn];
int main(){
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    cin>>n;
    for(int i=1;i<=n;i++)cin>>l[i]>>r[i];
    cin>>q;
    for(int i=1;i<=q;i++){
        ll sx,sy,tx,ty,lst,ans=0;
        cin>>sx>>sy>>tx>>ty;
        if(sx>tx)swap(sx,tx),swap(sy,ty);
        lst=sy;
        for(int j=sx+1;j<=tx;j++){
            if(lst<l[j])ans+=l[j]-lst,lst=l[j];
            else if(lst>r[j])ans+=lst-r[j],lst=r[j];
            ans++;
        }
        ans+=abs(ty-lst);
        cout<<ans<<'\n';
    }
    return 0;
}

B. 背包(knapsack)

难度:绿
\(dp\) 题。
在背包 \(dp\) 的同时维护价格大于 \(j\) 的珠宝的最大美丽度和即可。


#include<bits/stdc++.h>
#define mxn 10010
#define ll long long
#define pii pair<int,int>
#define mp make_pair
using namespace std;
ll n,m,k,ans,dp[mxn],sum[mxn];
pii a[mxn];
priority_queue<int> q;
int main(){
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    cin>>n>>m>>k;
    for(int i=1;i<=n;i++){
        int v,w;cin>>v>>w;
        a[i]=mp(v,w);
    }
    sort(a+1,a+n+1);
    for(int i=n;i;i--){
        sum[i]=sum[i+1]+a[i].second;
        q.push(-a[i].second);
        if(q.size()>k)
            sum[i]+=q.top(),q.pop();
    }
    for(int i=1;i<=n;i++)
        for(int j=m;j>=a[i].first;j--){
            dp[j]=max(dp[j],dp[j-a[i].first]+a[i].second);
            ans=max(ans,dp[j]+sum[i+1]);
        }
    cout<<ans;
    return 0;
}

C. 填色(paint)

难度:蓝
假设 \(ans_i\) 为大小为 \(i\) 时的答案,\(cnt_i\) 为大小为 \(i\) 时的极大交替组数量。
则有:

\[ans_i=\sum\limits_{k\geq i}(k-i+1)*cnt_k=ans_{i+1}+\sum\limits_{k\geq i}cnt_k \]

考虑用线段树进行维护。


#include<bits/stdc++.h>
#define ll long long
#define mxn 200010
#define sll set<ll>::iterator
using namespace std;
set<ll> st;
ll n,q,col[mxn<<1],sum1[mxn<<2],sum2[mxn<<2];
namespace seg{
    void push_up(int rot){
        sum1[rot]=sum1[rot<<1]+sum1[rot<<1|1];
        sum2[rot]=sum2[rot<<1]+sum2[rot<<1|1];
    }
    void update(int rot,int l,int r,int pos,int val){
        if(l==r){
            sum1[rot]+=val,sum2[rot]+=val*pos;
            return;
        }
        int mid=(l+r)>>1;
        if(pos<=mid)update(rot<<1,l,mid,pos,val);
        else update(rot<<1|1,mid+1,r,pos,val);
        push_up(rot);
    }
    ll query(ll* tre,int rot,int l,int r,int x,int y){
        if(r<x||l>y)return 0;
        if(l>=x&&r<=y)return tre[rot];
        ll ret=0,mid=(l+r)>>1;
        if(mid>=l)ret+=query(tre,rot<<1,l,mid,x,y);
        if(mid<r)ret+=query(tre,rot<<1|1,mid+1,r,x,y);
        return ret;
    }   
}
namespace sol{
    ll lst,sze;
    void add(ll l,ll r){
        if(l>n)return;
        if(r<n)seg::update(1,1,n,r-l+1,1);
        else lst=l,sze=r-l+1;
    }
    void merge(ll x){
        sll it=st.find(x),it1,it2;
        it1=it2=it;
        it1--,it2++;
        if(x<=n)seg::update(1,1,n,x-*it1,-1);
        if(*it2<=n)seg::update(1,1,n,*it2-x,-1);
        st.erase(it);
        add(*it1,*it2-1);
    }
    void split(ll x){
        sll it1=st.lower_bound(x),it2=it1;
        it1--;
        if(*it2<=n)seg::update(1,1,n,*it2-*it1,-1);
        st.insert(x);
        add(*it1,x-1);
        add(x,*it2-1);
    }
}
int main(){
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    cin>>n>>q;
    for(int i=1;i<=n;i++){
        cin>>col[i];
        col[i+n]=col[i];
    }
    st.insert(1);
    int lst=1;
    for(int i=1;i<=2*n;i++){
        if(i==2*n||col[i]==col[i+1]){
            sol::add(lst,i);
            st.insert(i+1);
            lst=i+1;
        }
    }
    while(q--){
        ll opt,x,y;cin>>opt;
        if(opt==1){
            cin>>x;
            ll ans=seg::query(sum2,1,1,n,x,n);
            ans-=(x-1)*seg::query(sum1,1,1,n,x,n);
            if(sol::sze>=x)
                ans+=min(n-sol::lst+1,sol::sze-x+1);
            cout<<ans<<'\n';
        }
        else{
            cin>>x>>y;
            x++;
            if(col[x]==y)continue;
            if(x>1){
                if(col[x]==col[x-1])sol::merge(x);
                else sol::split(x);
            }
            if(col[x]==col[x+1])sol::merge(x+1);
            else sol::split(x+1);            
            if(col[x+n]==col[x+n-1])sol::merge(x+n);
            else sol::split(x+n);
            if(x<n){
                if(col[x+n]==col[x+n+1])sol::merge(x+n+1);
                else sol::split(x+n+1);
            }
            col[x]=col[x+n]=y;
        }
    }
    return 0;
}

D. 小镇(town)

难度:紫

首先,我们先想一想要加几条特殊道路。

我们知道无向图中存在欧拉回路的条件是不存在度数为奇数的点。

而使一个连通图存在欧拉回路的方法为加入 度数为奇数的点数\(/2\) 条边。但这里还有很多可选道路,考虑如何处理。

记全体必经道路为 \(E_1\),可经道路为 \(E_2\)。记录每个结点在图 \(G_1=\langle V,E_1\rangle\) 中的度数,记作 \(deg_i\)。把点按照 \(deg_i\) 的奇偶性分成两类,奇为 \(A\) 点,偶为 \(B\) 点。

我们要用 \(E_2\) 中的边将 \(A\) 点两两相连。设图 \(G_2=\langle V,E_2\rangle\)。将 \(G_2\) 分成几个极大联通子图,设这些子图中有偶数个 \(A\) 点的称为 \(C\) 块,有奇数个的称为 \(D\) 块。

容易证明,\(C\) 块中的 \(A\) 点是可以通过 \(C\) 块中的路径两两相连的。\(D\) 块中的 \(A\) 点不能两两相连,因为到最后总会剩下一个 \(A\) 点。所以,特殊通道的数量为 \(D\) 块数量的一半。

然后想想至少走几条可选道路。

观察性质,我们发现 \(G_2\) 由一些基环树和树组成。

对于树,我们可以在上面加一个自环让其变成基环树。

现在我们考虑环上的每个点分出去的子树中最少需要走多少条可选道路。

若为 \(C\) 块,则树上只有一种情况,而环上则为两种,\(dfs\) 计算出即可。

若为 \(D\) 块,则可以 \(dp\) 出每个点连接特殊道路后最少走多少条。

懒得自己写了,就把官方solution放出来吧。


#include<bits/stdc++.h>
#define mxn 1000002
#define pb push_back
#define mp make_pair
#define pob pop_back
#define pii pair<int,int>
using namespace std;
vector<pii> e[mxn];
vector<int> vc,circle;
int c,type,n,m;
int deg[mxn],vis[mxn],sz[mxn];
int f[mxn],a[mxn],g[mxn];
int ans1,ans2;
void dfs1(int x,int& odd,int& nd,int& eg){//求出c块和d块数
	vis[x]=1;
	odd+=deg[x]&1;
	++nd;
	eg+=e[x].size();
	for(auto i:e[x])
		if(!vis[i.first])
			dfs1(i.first,odd,nd,eg);
}
bool dfs2(int x,int lst){//找环用
	vc.pb(x);
	vis[x]=2;
	for(auto i:e[x]){
		if(i.second!=lst){//不走父亲
			if(vis[i.first]==1){//尚未走过
				if(dfs2(i.first,i.second))
					return 1;
			} 
            else{//不走父亲但还是遇到了,说明是一个环
				while(vc.back()!=i.first){
					circle.pb(vc.back());
					vis[vc.back()]=3;//给环再打个标记
					vc.pob();
				}
				circle.pb(i.first);
				vis[i.first]=3;
				return 1;//有环,return 1
			}
        }
    }
	vc.pob();
	return 0;
}
int dfs3(int x,int lst){//C块上的dfs
	int ret=0;
	sz[x]=deg[x]&1;//看有多少A点
	for(auto i:e[x]){
		if(vis[i.first]!=3&&i.first!=lst){//若不是环上的点
			ret+=dfs3(i.first,x);
			sz[x]+=sz[i.first];
		}
    }
	return ret+(sz[x]&1);
}
int dfs4(int x,int lst){//D块上的dfs 相当于一个树形dp
	int ret=f[x];
	for(auto i:e[x]){
		if(vis[i.first]!=3&&i.first!=lst){
			f[i.first]=f[x]+(sz[i.first]&1?-1:1);
            //
			ret=min(ret,dfs4(i.first,x));
		}
    }
	return ret;
}
int main(){
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    cin>>c>>type>>n>>m;
	for(int i=1;i<=m;++i){
		int u,v,w;
        cin>>u>>v>>w;
		if(w)++deg[u],++deg[v]; 
        else{
			e[u].pb(mp(v,i));
			e[v].pb(mp(u,i));
		}
	}
	for(int i=1;i<=n;++i){
		if(!vis[i]){//对于每一个连通分支
			int odd=0,nd=0,eg=0;
			dfs1(i,odd,nd,eg);
			if(odd&1)++ans1;//若为D块,ans1++
			if(eg==(nd-1)*2)//如果是一棵树
				e[i].pb(mp(i,m+1));//就建一个自环,当作环处理
			vc.clear();
			circle.clear();
			dfs2(i,0);
			int sum=0;
			for(int j:circle)//对于环上的每个点,算出其每个分支要多少
                sum+=dfs3(j,0);
			if(!(odd&1)){//为C块,自行处理
				int lst=0,s=0,cnt=0;
				for(int j:circle){
					++cnt;//cnt:环上有多少点
					if(sz[j]&1){//有多出来的
						--sum;//剩余的地方,dfs的时候实际上是多算了一次长度,要减掉
						if(lst)s+=cnt-lst,lst=0; 
                        else lst=cnt;
					}
				}
				ans2+=sum+min(s,cnt-s);//环上一共就两种情况,看一下哪个更小就行了
				continue;
			}
            else{//D块 总会有一个点连不上
                int cur=INT_MAX,op=-1,cnt=0,len=0;
                for(int j:circle){
                    ++len;
                    if(sz[j]&1)sum--,a[len]=op,op=-op; 
                    else a[len]=0; 
                    g[len]=g[len-1]+a[len]*len;
                }
                int lst=0;//枚举每一个A点需要与别的D块连
                for(int j:circle){
                    ++cnt;
                    int t1=dfs4(j,0),t2;
                    if(sz[j]&1){
                        t2=g[lst]-g[len]+g[cnt];
                        lst=cnt;
                    } 
                    else t2=g[lst]-a[lst]*cnt-g[len]+g[cnt];
                    cur=min(cur,t1+min(t2,len-t2));
                }
                ans2+=sum+cur;
            }
		}
    }
	ans1/=2;//每两个D块连一条边
    if(type==1)cout<<ans1;
    else cout<<ans2;
	return 0;
}