NOIP2024模拟3:一路破冰
NOIP2024模拟3:一路破冰
雨后的青山。——240316
A-无向图删边
-
一句话题面:规定一轮中的删边方式为:按边权递减且每轮删掉的边集中没有环。问每条边会在第几轮被删除。
-
暴力的想法就是跑 \(k\) 轮最大生成树。
-
优化分两步:
-
1、主客转化:暴力是求每一轮中会删那些边,以轮数为主体;现在我们考虑每条边会在那一轮被删除,即以点为主体。进一步的来讲,在保证边权递减的删除方式下,一条边最早会在哪一轮不连通。
-
2、玄学优化:\(n\) 很小,因此会有很多权值不同的重边,记 \(las[x][y]\) 表示 \((x,y)\) 之间的上一条边是在哪一层并查集被删掉的,显然 \(las[x][y]\) 只变大不减小,每次寻找踩在上一次的基础上,越到后面效率越高。
#include<bits/stdc++.h> #define F(i,l,r) for(int i(l);i<=r;++i) using namespace std; const int N=3e5+5; struct node{ int u,v,w,id; inline bool operator < (const node &other )const{ return w>other.w; } }e[N]; int n,m,k,fa[10005][1005],ans[N],las[1005][1005]; inline int get(int x,int dep){return (fa[dep][x]==x)?x:fa[dep][x]=get(fa[dep][x],dep); } inline bool merge(int x,int y,int dep){ int fx=get(x,dep),fy=get(y,dep); if(fx==fy) return false; fa[dep][fx]=fy; return true; } signed main(){ // freopen("a.in","r",stdin); // freopen("a.out","w",stdout); ios::sync_with_stdio(0); cin.tie(0); cout.tie(0); cin>>n>>m>>k; F(i,1,m) { int u,v,w; cin>>u>>v>>w; e[i]=(node){u,v,w,i}; } F(i,0,k) F(j,1,n) fa[i][j]=j;//init std::sort(e+1,e+m+1); memset(ans,0,sizeof(ans)); F(i,1,m){ int u=e[i].u,v=e[i].v,id=e[i].id; if(u>v) swap(u,v); while(las[u][v]<=k && !merge(u,v,las[u][v])) ++las[u][v]; ans[id]=(las[u][v]>=k)?0:++las[u][v]; } F(i,1,m) cout<<ans[i]<<'\n'; return 0; }
B-黑白棋盘
- 一句话题面:每次任选一对 \((i,j)\), 用第 \(i\) 行去覆盖第 \(j\) 列,问涂成全黑的最少操作次数。
- 不难发现存在一行全黑是能全涂成全黑的充要条件。
- 若已经存在一行全黑了,那么操作次数就是非全黑的列数。
- 则现在问题转换成了:如何尽快涂出一行全黑?
- 对于第 \(i\) 行,要想涂成全黑,只需要第 \(i\) 列上存在黑点即可。
- 记
num[i]
表示第 \(i\) 行上白点个数,col[i]
表示第 \(i\) 列上黑点个数,那么将第 \(i\) 行变成全黑得代价是:
-
那第 \(i\) 列上不存在黑点呢?则需要额外一次操作来造一个黑。
-
因此
-1
的情况也浮出水面了:当且仅当棋盘全白。 -
那么逻辑链中还有最后一个问题:在变出一行全黑的过程中,非全黑列数是否会改变?
-
不会,因为存在一行全黑是能全涂成全黑的充要条件。
-
算法思路如上。
-
考场上应该优先多想一下的!毕竟放在第2题的位置,不要害怕时间不够!
#include<bits/stdc++.h>
#define F(i,l,r) for(int i(l);i<=r;++i)
using namespace std;
const int N=1005;
char s[N][N];
int num[N],col[N];
int n,cnt=0,tag=0,ans=1e9;
signed main(){
// freopen("b.in","r",stdin);
// freopen("b.out","w",stdout);
ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
cin>>n;
F(i,1,n) cin>>(s[i]+1);
F(i,1,n) F(j,1,n){
if(s[i][j]=='#') col[j]++,tag=1;//col[j]:the number of black in column <j>
else ++num[i];
}
if(!tag) return cout<<"-1",0;
F(i,1,n) if(col[i]!=n) ++cnt;//the number of column in which there is at least one white.
F(i,1,n) ans=min(ans,cnt+num[i]+(col[i]==0));
cout<<ans;
return 0;
}
upd:2024/03/22
C-序列
-
一句话题面:给定两个数 \(n, m\),求有多少长度为 \(2m\) 的序列 \(A\) 满足 :
-
\(A\) 中每个元素都是 \(n\) 的因数
-
\(A\) 所有项的乘积不超过 \(n^m\) 。答案对 \(998244353\) 取模。
-
-
【标签】转化(容斥)、数学、动态规划
-
注意到乘积最大是 \(n^{2m}\), 所以每一种乘积小于 \(n^m\) 的方案都对应着一种乘积大于 \(n^m\) 的方案。
-
即这两类方案一一对应,数量相等。
-
记乘积等于 \(n^m\) 的方案数为 \(tot\), 不限制总乘积,只满足限制1的方案数为 \(sum\). 那么答案即为:
- 问题转化为了如何求 \(tot\).
- 先对 \(n\) 进行质因数分解,根据 算术唯一分解定理,\(n=\sum{p_k^{a_k}}\)
- 不难发现每个质因子的贡献是独立的。
- 对于质因子 \(p_k\), 记 \(f[i][j]\) 表示截止第 \(i\) 个位置上一共用了 \(j\) 个该质因子的方案数,则有转移:
-
\(f[2m][m*a[k]]\) 即为每个质因子对答案的贡献。相乘即为 \(sum\).
-
边界条件为 \(f[i][0]=1\).
-
两个优化:1.第一位可以滚动数组 2.前缀和(有点儿难写,比赛建议不用前缀和写)
我的写法:
#include<bits/stdc++.h>
#define F(i,l,r) for(int i(l);i<=r;++i)
#define G(i,r,l) for(int i(r);i>=l;--i)
using namespace std;
using ll = long long;
const int N=1e6+5;
const int mod=998244353;
int p[N],a[N],f[N],sum[N];
int n,m,cnt=0,ans=1,tot=1,cz=1;
signed main(){
freopen("c.in","r",stdin);
freopen("c.out","w",stdout);
ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
cin>>n>>m;
int x=n;
for(int i=2;i<=n&&x>1;++i){
if(x%i==0){
p[++cnt]=i;
while(x%i==0) ++a[cnt],x/=i;
cz=(1ll*cz*(a[cnt]+1))%mod;
}
}
F(k,1,cnt){//every prime
memset(sum,0,sizeof(sum));
memset(f,0,sizeof(f));
F(i,0,a[k]*m) sum[i]=i+1;
F(i,2,2*m){
F(j,1,(a[k]*m)){
if(j<=a[k]) f[j]=sum[j];
else f[j]=(1ll*sum[min((i-1)*a[k],j)]-sum[j-a[k]-1])%mod;
}
sum[0]=1; F(j,1,m*a[k]) sum[j]=(1ll*sum[j-1]+f[j])%mod;
}
ans=(1ll*ans*f[a[k]*m])%mod;
}
F(i,1,2*m) tot=(1ll*tot*cz)%mod;
cout<<((1ll*tot+ans)%mod*499122177%mod+mod)%mod;
return 0;
}
STD写法:
F(k,1,cnt){//every prime
memset(f,0,sizeof(f));
f[0]=1;
F(i,1,2*m){
sum[0]=1; F(j,1,m*a[k]) sum[j]=(1ll*sum[j-1]+f[j])%mod;
F(j,1,a[k]*m)
f[j]=(1ll*sum[j]-(j-a[k]-1<0?0:sum[j-a[k]-1]))%mod;
}
ans=(1ll*ans*f[a[k]*m])%mod;
}
- 反思:考场上应该先不加前缀和,最后要优化再加。