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;
}