10 月 4 日 S 組 風 雨 大 作

Second_coming發表於2024-10-05

智障行為+2

T1 T2 T3 T4
0 0 0 0

好吧至少下一次不會考更低了

T1

你有個 n 個點 m 條邊的無向圖,每條邊都有紅藍兩種顏色中的一種,保證紅色的邊形成了這個圖的一個生成樹。
你希望給這些邊賦上邊權,保證邊權是 1 ∼ m 的排列,使得紅色的邊是最小生成樹。
希望這些邊權形成的序列字典序最小,也就是先比較第一條邊的邊權,再比較第二條邊的邊權,依次類推。
對於所有資料,保證 n, m ≤ 5 × 10^5 , m ≥ n − 1。

對,這道題(感覺像綠)卡了我很久,掛了

Time:2s
Memory:128M

正確思路:

首先,我們先看一張圖:

綠字代表加入順序(邊序)

根據題意,我們會發現:紅邊會構成一棵樹,之後每加入一條藍邊就會構成一個環(除了這條邊其他環上邊均為紅邊),由於藍邊一定是環上最大邊(紅邊為最小生成樹上的邊,一定比他小),所以我們就可以用拓撲排序解決此題(時間複雜度n方,TLE)

我們可以嘗試對上述方法用資料結構進行最佳化,但時間複雜度還是會有問題(線段樹時間可能沒問題,but MLE)

我們可以考慮從前往後,按順序貪心確定每條邊的大小。對於一條非樹邊,考慮有k條它對應的樹邊沒
確定,那麼把這些樹邊按順序從小到大賦值,然後把這條樹邊賦上環上最大紅邊的邊權+1即可。因為一條邊被賦完值之
後不會再被修改,所以可以用並查集實現,也就是確定的邊直接縮起來,時間複雜度O(nlogn) 。

code:

#include <bits/stdc++.h>
// #include<windows.h>
using namespace std;
// #pragma GCC optimize(2)
#define int long long
#define pii pair<int, int>
#define il inline
#define p_q priority_queue
#define map unordered_map
#define rg register


const int N1 = 100005;
const int N2 = 1000006;
const int mod = 998244353;

#define debug 0
#define endl '\n'

int _test_ = 1;
int n,m;
vector<pii> a[N1*5];
struct edge{
    int x,y,col;
}e[N2];
int fa[N1*5],deep[N1*5];
int tot=0;
int top[N2],ans[N2];
int fid[N1*5];// 用來存這個節點連向父親的邊的id
namespace third_coming {

    void dfs(int x,int f,int dep){
        fa[x]=f;
        deep[x]=dep;
        for(int i=0;i<a[x].size();i++){
            if(f==a[x][i].first){
                continue;
            }
            fid[a[x][i].first]=a[x][i].second;
            dfs(a[x][i].first,x,dep+1);
        }
    }

    int asdf[5*N1];
    void add_edge(int id){
        if(e[id].col){//紅邊
            if(!ans[id]) ans[id]=++tot;//沒有記錄過ans
            return;//紅邊不需要判斷環
        }
        int x=e[id].x,y=e[id].y;
        int u=x,v=y,tt=0;//tt,asdf是用來記錄環上沒有ans的紅邊的id
        while(top[u]!=top[v]){
            // cout<<u<<' '<<v<<endl;
            // cout<<top[u]<<' '<<top[v]<<endl;
            // cout<<fa[u]<<' '<<fa[v]<<endl;
            // Sleep(100);
            if(deep[top[u]]>deep[top[v]]){
                if(!ans[fid[top[u]]]) asdf[++tt]=fid[top[u]];
                u=fa[top[u]];
            }//u比v深,u往上爬
            else{
                if(!ans[fid[top[v]]]) asdf[++tt]=fid[top[v]];
                v=fa[top[v]];
            }//v比u深,v往上爬
        }//LCA+環上計算紅邊
        sort(asdf+1,asdf+tt+1);
        for(int i=1;i<=tt;i++){
            int x=asdf[i];
            // pq.pop();
            ans[x]=++tot;
        }
        ans[id]=++tot;
        int p=u,q=v;
        u=x,v=y;
        while(top[u]!=top[v]){
            if(deep[top[u]]>deep[top[v]]){
                // int p=top[u];
                int x=u;
                u=fa[top[u]];
                top[x]=top[p];
            }
            else{
                int x=v;
                v=fa[top[v]];
                top[x]=top[q];
            }
        }//路徑壓縮
    }
	void init() {
        // cout<<endl;
        cin>>n>>m;
        for(int i=1;i<=m;i++){
            // int x,y,z;
            cin>>e[i].x>>e[i].y>>e[i].col;
            if(!e[i].col) continue;
            a[e[i].x].push_back({e[i].y,i});
            a[e[i].y].push_back({e[i].x,i});
        }
        dfs(e[1].x,0,1);//預處理
        for(int i=1;i<=n;i++){
            top[i]=i;
        }//top用於LCA的運算(最佳化)
        for(int i=1;i<=m;i++){
            add_edge(i);
        }//一條一條加進去算
        for(int i=1;i<=m;i++){
            cout<<ans[i]<<" ";
        }
	}

	void solve() {

	}

	void main() {
		init();
		solve();
	}
}
using namespace third_coming;
signed main() {
     ios::sync_with_stdio(0);
     cin.tie(0);
     cout.tie(0);
#ifdef debug
	// freopen("series.in", "r", stdin);
	// freopen("series.out", "w", stdout);
#endif
	third_coming::main();
	return 0;
}

T2

現在有 n 個區間 [li, ri],每個區間有個權值 wi。我們把這 n 個區間當成 n 個點,如果兩個區間它們之間有交(包括端點),那麼我們就在這兩個區間之間連邊,形成了一個區間圖。
現在希望你刪除一些區間,使得每個連通塊大小不超過 k。輸出刪除區間最小的權值和。

保證 1 ≤ k ≤ n ≤ 2500, 1 ≤ li ≤ ri ≤ 10^9(區間左右端點), 1 ≤ wi ≤ 10^9。

Time:1s
Memory:1G