1. 程式人生 > 實用技巧 >ARC107 遊記

ARC107 遊記

目錄

ARC107 遊記

這場相比上次還算好吧,漲了一點分。

F 比賽時還是沒有想出來,網路流建模果然還是不太熟悉。

以後只寫 D,E,F 的題解算了,前三題就不寫了。

D Number of Multisets

題意簡述

詢問有多少個可重集 \(S\) 滿足 \(|S|=n\) 並且 \(S\) 中所有元素的和為 \(k\) 並且 \(S\) 中的元素都可以表示為 \(\frac{1}{2^x}(x\ge 0)\)

\(2\) 的非負整數次冪的倒數),答案對 \(998244353\) 取模。

\(1\le k\le n\le 3000\)

題目分析

考慮 dp ,設 \(dp(i,j,t)\) 表示滿足 \(|S|=i\) 並且 \(S\) 中所有元素的和為 \(j\times \frac{1}{2^t}\) 並且 \(S\) 中的元素都可以表示為 \(\frac{1}{2^x}(x\ge t)\) 的可重集 \(S\) 的數量,不難發現, \(dp(i,j,t)\) 的值其實和 \(t\) 無關,所以我們可以把 \(t\) 這一維去掉,只設 \(dp(i,j)\)

轉移可以列舉有多少個元素是 \(\frac{1}{2^t}\)

\[dp(i,j)=\sum_{k=0}^idp(i-k,(j-k)\times 2) \]

另一種轉移方法,看是否存在一個元素 \(\frac{1}{2^t}\)

\[dp(i,j)=dp(i-1,j-1)+dp(i,j\times 2) \]

使用第二種轉移方法,時間複雜度 \(\mathcal O(nk)\) ,當然可以通過推式子的方式由第一種轉移方法推出第二種轉移方法。

參考程式碼

#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define ch() getchar()
#define pc(x) putchar(x)
template<typename T>inline void read(T&x){
	int f;char c;
	for(f=1,c=ch();c<'0'||c>'9';c=ch())if(c=='-')f=-f;
	for(x=0;c<='9'&&c>='0';c=ch())x=x*10+(c&15);x*=f;
}
template<typename T>inline void write(T x){
	static char q[64];int cnt=0;
	if(!x)pc('0');if(x<0)pc('-'),x=-x;
	while(x)q[cnt++]=x%10+'0',x/=10;
	while(cnt--)pc(q[cnt]);
}
const int maxn=3005,mod=998244353;
int mo(const int x){
	return x>=mod?x-mod:x;
}
int dp[maxn][maxn];
int main(){
	int n,k;read(n),read(k);
	dp[0][0]=1;
	for(int i=1;i<=n;++i){
		for(int j=i;j>=1;--j){
			dp[i][j]=mo(dp[i-1][j-1]+(j*2<=i?dp[i][j*2]:0));
		}
	}
	write(dp[n][k]),pc('\n');
	return 0;
}

E Mex Mat

比賽時被這題卡了好久,好在最後想出來了。

題意簡述

\(n\times n\) 的矩陣 \(a\) ,其中的每個數的取值為 \(\{0,1,2\}\) ,矩陣滿足 \(a_{x,y}=\mbox{mex}(a_{x-1,y},a_{x,y-1})(x\ge 2,y\ge 2)\) ,現在給出 \(a_{1,i}\)\(a_{i,1}\) ,詢問整個矩陣中 \(0,1,2\) 的出現次數。

\(1\le n\le 5\times 10^5\)

題目分析

如果 \(a_{x,y}=0\) ,那麼 \(a_{x+1,y}\)\(a_{x,y+1}\) 就非 \(0\) ,那麼 \(a_{x+1,y+1}=0\) ,所以一個 \(0\) 會沿斜線傳遞下去。

如果兩個 \(0\) 相鄰 1 ,那麼就會變成這樣:

0 0
 010
  010
   010
    010
     ...

如果兩個 \(0\) 相鄰 2 ,那麼就會變成這樣:

0 20
 0120
  0120
   0120
    0120
     0120
      ....

或者這樣:

0 10
 0210
  0210
   0210
    0210
     0210
      ....

兩個 \(0\) 在執行了若干步後要麼相鄰 \(1\) 要麼相鄰 \(2\) ,所以在執行若干步後必然滿足 \(a_{x+1,y+1}=a_{x,y}\) ,所以我們就可以先往下往右遞推出前若干步,然後就可以直接計算了。

我的程式選擇了遞推前 \(10\) 行和前 \(10\) 列,官方題解好像是遞推了前 \(4\) 行,好像是說枚舉了前 \(4\) 行的所有情況就能發現必然有 \(a_{5,5}=a_{4,4}\)

參考程式碼

#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define ch() getchar()
#define pc(x) putchar(x)
template<typename T>inline void read(T&x){
	int f;char c;
	for(f=1,c=ch();c<'0'||c>'9';c=ch())if(c=='-')f=-f;
	for(x=0;c<='9'&&c>='0';c=ch())x=x*10+(c&15);x*=f;
}
template<typename T>inline void write(T x){
	static char q[64];int cnt=0;
	if(!x)pc('0');if(x<0)pc('-'),x=-x;
	while(x)q[cnt++]=x%10+'0',x/=10;
	while(cnt--)pc(q[cnt]);
}
const int maxn=500005;
long long cnt[3];
int Ma[3][3]={{1,2,1},{2,0,0},{1,0,0}};
int A[maxn],B[maxn];
int main(){
	int n;read(n);
	for(int i=1;i<=n;++i)read(A[i]),++cnt[A[i]];
	for(int i=2;i<=n;++i)read(B[i]),++cnt[B[i]];
	if(n<=10){
		for(int i=2;i<=n;++i){
			A[i-1]=B[i];
			for(int j=i;j<=n;++j)
				++cnt[A[j]=Ma[A[j]][A[j-1]]];
			B[i]=A[i];
			for(int j=i+1;j<=n;++j)
				++cnt[B[j]=Ma[B[j]][B[j-1]]];
		}
	}
	else{
		for(int i=2;i<=10;++i){
			A[i-1]=B[i];
			for(int j=i;j<=n;++j)
				++cnt[A[j]=Ma[A[j]][A[j-1]]];
			B[i]=A[i];
			for(int j=i+1;j<=n;++j)
				++cnt[B[j]=Ma[B[j]][B[j-1]]];
		}
		for(int i=10;i<=n;++i)
			cnt[A[i]]+=n-i;
		for(int i=11;i<=n;++i)
			cnt[B[i]]+=n-i;
	}
	write(cnt[0]),pc(' '),write(cnt[1]),pc(' '),write(cnt[2]),pc('\n');
	return 0;
}

F Sum of Abs

題意簡述

\(n\) 個點 \(m\) 條邊的無向圖,你可以花費 \(A_i\) 的代價刪除 \(i\) 點,最終你的收益是所有連通塊 \(B_i\) 的和的絕對值之和,請最大化收益減代價和。

\(1\le n,m\le 300,1\le A_i\le 10^6,-10^6\le B_i\le 10^6\)

題目分析

資料範圍極力暗示網路流,問題就是如何建模。

假如 \(S\) 中的點構成了一個連通塊,那麼最後對收益的貢獻就是 \(\max(\sum_{u\in S}B_u,-\sum_{u\in S}B_u)=\max(\sum_{u\in S}B_u,\sum_{u\in S}(-B_u))\) ,也就是說,在同一個連通塊中的點的 \(B_i\) 對答案造成貢獻時乘以的係數是相同的,即乘以 \(1\) 或者 \(-1\)

每個點有三種選擇:刪除(給答案貢獻 \(-A_i\) ),給答案貢獻 \(B_i\) 、給答案貢獻 \(-B_i\) 。三選一模式,考慮使用最小割模型,將點 \(u\) 拆成兩個點 \(u_0,u_1\) ,然後連邊方法是 \(S\to u_0\to u_1\to T\) ,割掉 \(S\to u_0\) 表示給答案貢獻 \(B_i\) ,割掉 \(u_0\to u_1\) 表示刪除點 \(u\) (給答案貢獻 \(-A_i\) ),割掉 \(u_1\to T\) 表示給答案貢獻 \(-B_i\) ,這樣的話在同一個連通塊裡的點必須選擇在同一側,即要麼都和 \(S\) 相連要麼都和 \(T\) 相連。

連邊是這樣的: \(S\to u_0\) 流量為 \(-B_i\)\(u_0\to u_1\) 流量為 \(A_i\)\(u_1\to T\) 流量為 \(B_i\) 。由於流量不能為負數,所以三條邊流量和答案需要集體加上 \(\mbox{abs}(B_i)\)

如果在原圖中 \(u,v\) 有連邊,說明 \(u,v\) 必須要在同一邊,也就是不能同時滿足割掉 \(S\to u_0\) 並且割掉 \(v_1\to T\) (此時相當於保留了 \(u_0\to u_1\to T\)\(S\to v_0\to v_1\) ),或者不能同時滿足割掉 \(u_1\to T\) 並且割掉 \(S\to v_0\) (此時相當於保留了 \(S\to u_0\to u_1\)\(v_0\to v_1\to T\) ),所以 \(v_1\to u_0\) 流量 \(\infty\)\(u_1\to v_0\) 流量 \(\infty\)

參考程式碼

#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define inf 0x3f3f3f3f
using namespace std;
#define ch() getchar()
#define pc(x) putchar(x)
template<typename T>inline void read(T&x){
	int f;char c;
	for(f=1,c=ch();c<'0'||c>'9';c=ch())if(c=='-')f=-f;
	for(x=0;c<='9'&&c>='0';c=ch())x=x*10+(c&15);x*=f;
}
template<typename T>inline void write(T x){
	static char q[64];int cnt=0;
	if(!x)pc('0');if(x<0)pc('-'),x=-x;
	while(x)q[cnt++]=x%10+'0',x/=10;
	while(cnt--)pc(q[cnt]);
}
const int maxn=305,maxm=305;
struct Edge{
	int v,w,nt;
	Edge(int v=0,int w=0,int nt=0):
		v(v),w(w),nt(nt){}
}e[maxm*4+maxn*4];
int hd[maxn*2],num=1;
void qwq(int u,int v,int w){
	e[++num]=Edge(v,w,hd[u]),hd[u]=num;
}
void qvq(int u,int v,int w){
	qwq(u,v,w);qwq(v,u,0);
}
int S=0,T=1,dis[maxn*2],q[maxn*2];
int bfs(void){
	memset(dis,0,sizeof dis);
	int fro=0,bac=0;dis[q[bac++]=S]=1;
	while(fro<bac){
		int u=q[fro++];
		for(int i=hd[u];i;i=e[i].nt){
			int v=e[i].v,w=e[i].w;
			if(w&&!dis[v]){
				dis[q[bac++]=v]=dis[u]+1;
			}
		}
	}
	return dis[T];
}
int cur[maxn*2];
int dfs(int u,int ep){
	if(u==T)return ep;int re=0;
	for(int&i=cur[u];i;i=e[i].nt){
		int v=e[i].v,w=e[i].w;
		if(w&&dis[v]==dis[u]+1){
			int tmp=dfs(v,min(ep,w));
			re+=tmp;ep-=tmp;
			e[i].w-=tmp;e[i^1].w+=tmp;
			if(!ep)break;
		}
	}
	return re;
}
int dinic(void){
	int re=0;
	while(bfs()){
		memcpy(cur,hd,sizeof hd);
		re+=dfs(S,inf);
	}
	return re;
}
int A[maxn],B[maxn];
int main(){
	int n,m;read(n),read(m);
	for(int i=1;i<=n;++i)read(A[i]);
	for(int i=1;i<=n;++i)read(B[i]);
	int ans=0;
	for(int i=1;i<=n;++i){
		int a=A[i],b=B[i];
		if(b<0)ans-=b,qvq(S,i*2  ,-2*b),qvq(i*2,i*2+1,a-b);
		else   ans+=b,qvq(i*2+1,T, 2*b),qvq(i*2,i*2+1,a+b);
	}
	for(int i=1;i<=m;++i){
		int u,v;
		read(u),read(v);
		qvq(u*2+1,v*2,inf);
		qvq(v*2+1,u*2,inf);
	}
	write(ans-dinic()),pc('\n');
	return 0;
}

總結

前幾天還搞了圖論來著,結果 F 網路流建模還是沒有獨立想出來,自己的思維建模能力還是不夠啊。