10.5組隊訓練賽-2024CCPC山東省賽

zxsoul發表於2024-10-05

10.5組隊訓練賽-2024CCPC山東省賽

成績 4
排名 8(差3題)

寫在前面

I k a 是簡單題,但是因為 a 爆 long long 一直沒有看出來,導致交了很都發。
出現的問題就是程式碼能力太弱,不能保證一遍過。改錯的能力也很弱,沒有及時發現出錯的地方,一直在題意理解和演算法方面打轉。浪費時間。

J 題想了一個貪心感覺是正確的,事實證明就是正確的,但是不知道vector啥原因最後沒過,賽後看了兩個小時的題解,依然覺著沒有問題,最後才發現就是vector的問題,十分生氣,但也沒有辦法,就是菜

說說這次:大家人居7題,我也是無語了,我們直接被拉了3到,我也沒有想到成這樣,真的都這麼厲害嗎,還是自己不夠努力呀,哎,菜

J. Colorful Spanning Tree

題目大意:每個顏色有 \(a_i\) 個點,顏色與顏色之間有邊權,求所有點的最小生成樹。

思路

當時一個很明顯貪心思路就是,先形成數,然後貪心直接找最小的邊連。然後我就直接上去莽了,實時證明她是正確的,因為最小生成樹的性質,

最小生成樹裡面一定包含每個點的所有邊的最小邊,因為,如果不包含,新增最小邊一定形成一個環,這個時候用最小邊替換那條邊,仍然可以使圖聯通,並且總價值最小,因此結論成立。

問題就出現在維護最小值上面,一開始我很麻煩的用了vector 排序去做,結果發現過了一半的資料,當時就覺得我的貪心出現了問題,就卡住了。

其實問題也在我,如果知道這個生成樹的性質,就不會質疑我的演算法的問題,進而發現vector 出錯的機率就更大,需要長腦子了。

/*
	
	考點:貪心,最小生成樹板子 
	看了題解,看了題解程式碼,想了很久,結果本來就是對的
	因為vector的問題!!!
	
	不過透過這個題目也學到一些東西。
	
	最小生成樹裡面一定包含一個點的所有邊的最小邊
	因為,如果不包含,新增最小邊一定形成一個環
	這個時候用最小邊替換那條邊,仍然可以使圖聯通,並且總價值最小。
	因此結論成立。 
*/ 
#include<bits/stdc++.h>
#define int long long
using namespace std;
int read(){int x;scanf("%lld",&x);return x;}
const int B=1e6+10;
const int inf=0x3f3f3f3f;
int T;
int n;
int a[B];
vector<int>v[1000009];
int fa[B];
int find(int x)
{
	if (fa[x]==x) return x;
	else return fa[x]=find(fa[x]);
}
struct node
{
	int u,v,x;
}e[B];
int cmp(node a,node b)
{
	return a.x<b.x;
}
int minxx[B]; 
void work()
{
	n=read();
	int tot=0;
	for (int i=1;i<=n;i++) a[i]=read(),fa[i]=i,minxx[i]=0x3f3f3f3f;
	for (int i=1;i<=n;i++)
	{
		for (int j=1;j<=n;j++) 
		{
			int x=read();
			if (!a[i] || !a[j]) continue;
			e[++tot]={i,j,x};
			minxx[i]=min(minxx[i],x);
		}
	}
	int sum=0;
	sort(e+1,e+1+tot,cmp);
	for (int i=1;i<=tot;i++)
	{
//		if (e[i].u==e[i].v) continue; 
		int u=find(e[i].u);
		int v=find(e[i].v);
		if (fa[u]==fa[v]) continue;
		fa[v]=u;
		sum+=e[i].x;
	}
	for (int i=1;i<=n;i++)
	{
		int res=a[i]-1;
		if (res<=0) continue;
		sum+=minxx[i]*res;
	}
	cout<<sum<<endl;
}
signed main()
{
	cin.tie(0); 
	T=read();
	while (T--) work();
	return 0;
}

K - Matrix

題目大意

給一個 \(n\times n\) 的矩陣填數字,要求 \(1-2n\) 至少都存在一次,並且任意矩形的四個頂點,兩兩不同的情況只有一種

思路:

思維題,反正我是想不出來,但是感覺非常巧妙。

其實看做法發現還是挺有道理的。

首先確定那個不同的四個角,也就是第一行和最後一行的兩個數,然後,因為問我不想在其他行存在答案,所以我就直接把其他行的數字,每一行都相同,這樣就可以保證所有的行都不能在被用,然後再來看剩下的列,為了保證當取第一行和最後一行的數的時候相同,所以我們直接讓兩行剩下的數,每一列都是相同的數,然後這樣算下來剛好2n個,好神奇!!!!!

#include<bits/stdc++.h>
using namespace std;
int read(){int x;scanf("%d",&x);return x;}
const int B=1e6+10;
const int inf=0x3f3f3f3f;
int T;
int n;
int a[109][109];
void work()
{
	cin>>n;
	a[1][1]=1;
	a[1][2]=2;
	a[n][1]=3;
	a[n][2]=4;
	int now=2;
	for (int i=5;;i++)
	{
		a[1][++now]=i;
		a[n][now]=i;
		if (now>=n) 
		{
			now=i;
			break;
		}
	}
	puts("Yes");
	for (int i=1;i<=n;i++) cout<<a[1][i]<<" ";
	puts(""); 
	for (int i=2;i<n;i++)
	{
		now++; 
		for (int j=1;j<=n;j++)
		{
			cout<<now<<" ";
		} 
		cout<<"\n"; 
	}
	for (int i=1;i<=n;i++)
	{
		cout<<a[n][i]<<" ";
	}
}
int main()
{
	T=1;
	while (T--) work();
	return 0;
}

C. Colorful Segments 2

題目大意

現在給一些線段染色,每個線段相互獨立,有 k 中顏色,要求相交的線段不能染相同的顏色,求方案數。

思路:

顯然,我感覺我不會做,做不了一點,一開始以為是DP,結果發現時間複雜度不對,然後就做不出來了,我還以為是DP最佳化,然而推了半個小時的式子但是發現不對,草了

然後開始想組合數,然後發現,我依舊不會做,因為,組合數一點思路都沒有,我一直在想一個區間被多次相交,前後都相交,怎麼算。

然後我忘記了染色計數的一個高中感覺,沒錯,我只能稱為感覺,然後對於這種當前位置操作會對兩側都產生影響的問題,可以選擇從左邊依次來做,這樣也可以實現兩兩限制

就是高中相鄰格子不能染相同的顏色,或者物品填格子問題,做法就是從左到右計算出每個位置可以做出的貢獻,然後做乘法。

對於這道題目,當前面有 \(t\) 個與當前線段交叉,那麼線段的貢獻值為 \(k-t\)

然後直接做乘法就可以了。

解決交叉問題其實就是維護當前有多少個 \(r\) 在當前 \(l\) 的後面,可以用樹狀陣列,線段樹計數,也可以用佇列,維護出已經不滿足的 \(r\),每少一個 \(r\) 就意味著當前位置可以多天一種顏色,而每次計算完一個線段的時候,假設一個顏色不能用 k--,

假設的原因是保證 k 一直都是當前位置可以用的顏色個數,即使-1,如果然不會和下一個區間有交叉,同樣也會被加回來。

樹狀陣列程式碼
用到了離散化,
這裡強調一句:unique 的意思表示刪除相鄰相同的元素,不具備排序的能力

所以離散化需要先排序然後去重

/*
	先寫一個樹狀陣列的
	用樹狀陣列維護前面右端點在當前左端點之前的數量,求一個字尾和
	結果發現座標大小1e9
	
	直接離散化 
*/ 
#include<bits/stdc++.h>
#define int long long
using namespace std;
int read(){int x;scanf("%lld",&x);return x;}
const int B=1e6+10;
const int mod=998244353; 
const int inf=0x3f3f3f3f;
int T;
int n,k;
struct node
{
	int l,r;
}a[B];
int b[B];
int tot;
int cmp(node a,node b)
{
	if (a.l==b.l) return b.r<b.r;
	return a.l<b.l;
}
int t[B];
int lowbit(int x){return x&-x;}
void modify(int x,int y){for (int i=x;i<=tot;i+=lowbit(i)) t[i]+=y;}
int query(int x){int res=0;for (int i=x;i;i-=lowbit(i)) res+=t[i];return res;}
int find(int l,int r)
{
	return query(r)-query(l-1);
} 
void work()
{
	tot=0;
	cin>>n>>k;
	for (int i=1;i<=n;i++)
	{
		a[i].l=read();
		a[i].r=read();
		b[++tot]=a[i].l;
		b[++tot]=a[i].r;
	}
	sort(a+1,a+1+n,cmp);
	//離散化忘記排序,先排序,然後在去重,
	//unique 的作用:去掉相鄰相同的元素。 
	sort(b+1,b+1+tot);
	tot=unique(b+1,b+1+tot)-b-1;
	int ans=1;
	for (int i=1;i<=n;i++)
	{
		a[i].l=lower_bound(b+1,b+1+tot,a[i].l)-b;
		a[i].r=lower_bound(b+1,b+1+tot,a[i].r)-b;
		int t=find(a[i].l,tot);
		ans=(ans%mod*(k-t)%mod)%mod;
		modify(a[i].r,1);
	}
	cout<<ans%mod<<"\n";
	for (int i=1;i<=n;i++)//還原 
	{
		modify(a[i].r,-1);
	}
}
signed main()
{
	cin.tie(0);
	T=read();
	while (T--) work();
	return 0;
}

法二

優先佇列做法

/*
	先寫一個樹狀陣列的
	用樹狀陣列維護前面右端點在當前左端點之前的數量,求一個字尾和
	結果發現座標大小1e9
	
	直接離散化 
*/ 
#include<bits/stdc++.h>
#define int long long
using namespace std;
int read(){int x;scanf("%lld",&x);return x;}
const int B=1e6+10;
const int mod=998244353; 
const int inf=0x3f3f3f3f;
int T;
int n,k;
struct node
{
	int l,r;
}a[B];
int b[B];
int tot;
int cmp(node a,node b)
{
	if (a.l==b.l) return b.r<b.r;
	return a.l<b.l;
}
int t[B];
int lowbit(int x){return x&-x;}
void modify(int x,int y){for (int i=x;i<=tot;i+=lowbit(i)) t[i]+=y;}
int query(int x){int res=0;for (int i=x;i;i-=lowbit(i)) res+=t[i];return res;}
int find(int l,int r)
{
	return query(r)-query(l-1);
} 
void work()
{
	tot=0;
	cin>>n>>k;
	for (int i=1;i<=n;i++)
	{
		a[i].l=read();
		a[i].r=read();
	}
	sort(a+1,a+1+n,cmp);
	int ans=1;
	priority_queue<int>q;
	for (int i=1;i<=n;i++)
	{
		while (!q.empty() && -q.top()<a[i].l) k++,q.pop();
		ans=(ans%mod*k%mod)%mod;
		k--;
		q.push(-a[i].r);
	} 
	cout<<ans%mod<<endl;
}
signed main()
{
	cin.tie(0);
	T=read();
	while (T--) work();
	return 0;
}

相關文章