1. 程式人生 > 其它 >2021.06.21模擬賽

2021.06.21模擬賽

題面
寫在前面:總算正常發揮了一次沒有出降智錯誤,rank 4,好耶!

A.分割

DP+字首和+排序+離散化+樹狀陣列優化
\(f_i\) 是以i結尾的方案數,要注意 \(sum_i=0\) 的時候 \(f_i\) 也有一種方案。
\(code\)

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#define maxn 100010
#define int long long
using namespace std;
template<typename T>
inline void read(T &x){
	x=0;bool flag=0;char c=getchar();
	for(;!isdigit(c);c=getchar()) if(c=='-') flag=1;
	for(;isdigit(c);c=getchar()) x=x*10+(c^48);
	if(flag) x=-x;
}

const int mod=1000000009;
int n,x;
int b[maxn],t[maxn],f[maxn];
struct data{
	int sum;
	int id;
}a[maxn];

bool cmp(data x,data y){
	if(x.sum!=y.sum) return x.sum<y.sum;
	return x.id<y.id;
}

void add(int x,int k){
	for(;x<=n;x+=x&-x) (b[x]+=k)%=mod;
}

int ask(int x){
	int res=0;
	for(;x;x-=x&-x) (res+=b[x])%=mod;
	return res;
}

signed main(){
	read(n);
	for(int i=1;i<=n;i++){
		read(x);
		a[i].sum=a[i-1].sum+x;
		a[i].id=i;
		if(a[i].sum>=0) f[i]=1;
	}
	sort(a+1,a+n+1,cmp);
	for(int i=1;i<=n;i++) t[a[i].id]=i;
	for(int i=1;i<=n;i++){
		(f[i]+=ask(t[i]))%=mod;
		add(t[i],f[i]);
	}
	printf("%lld\n",f[n]);
	return 0;
}
/*
4
2
3
-3
1
//
4
*/

B.子序列

一開始求成子序列的數值之和了沒注意到是權值和
最後寫了30pts的暴搜,過了樣例1,樣例2過不去,一邊寫別的題一邊讓它接著跑樣例2,到了比賽結束跑了兩個半小時都沒跑完...中午吃飯忘了關程式,回來發現還在跑...跑了五個小時愣是沒跑完 \(n=1000\) 的樣例2...事實證明指數級的dfs果然慢的一批這不廢話嗎
先放一個能跑一年的暴搜:

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#define maxn 100010
#define int long long
using namespace std;
template<typename T>
inline void read(T &x){
	x=0;bool flag=0;char c=getchar();
	for(;!isdigit(c);c=getchar()) if(c=='-') flag=1;
	for(;isdigit(c);c=getchar()) x=x*10+(c^48);
	if(flag) x=-x;
}

const int mod=10007;
int n,a[maxn];
int cnt,k,mp[maxn],tong[maxn],ok[maxn],sum1,sum2;

int qpow(int x,int y){
	int res=1;
	for(;y;y>>=1,x=x*x%mod) if(y&1) res=res*x%mod;
	return res%mod;
}

void dfs(int *b,int siz,int *ok,int dep){
	if(siz==dep){
		int cnt=0;
		bool flag=0;
		memset(mp,0,sizeof(mp));k=0;
		for(int i=0;i<siz;i++){
//			cout<<b[i]<<"***"<<ok[i]<<" ";
			if(ok[i]){
				mp[++k]=i;
				if(!tong[b[i]]) tong[b[i]]=1,cnt++;
			}
		}
//		cout<<endl;
		
		sum1=(sum1+cnt)%mod;
//		cout<<sum1<<"*"<<endl;
//		cout<<mp[1]<<" ";
		for(int i=2;i<=k;i++){
			if(mp[i-1]+1!=mp[i]) flag=1;
//			cout<<mp[i]<<" ";
		}
//		cout<<endl;
		if(!flag) sum2=(sum2+cnt)%mod;
//		cout<<sum2<<"**"<<endl;
		for(int i=0;i<siz;i++){
			if(tong[b[i]]) tong[b[i]]=0;
		}
//		for(int i=0;i<siz;i++){
//			if(ok[i]) printf("%lld ",b[i]);
//		}
//		printf("\n");
//		cout<<"********"<<endl;
		return ;
	}
	else{
		ok[dep]=0;
		dfs(b,siz,ok,dep+1);
		ok[dep]=1;
		dfs(b,siz,ok,dep+1);
	}
}

signed main(){
//	freopen("ex_seq2.in","r",stdin);
	read(n);
	for(int i=0;i<n;i++) read(a[i]);
	dfs(a,n,ok,0);
	cout<<sum1<<" "<<sum2<<endl;
	return 0;
}
/*
3
2 3 2
//
10 9
*/

上正解
\(O(n)\) 列舉位置,把它變成計數類問題
第一個問題:求所有子序列的權值和
考慮當前這位數對答案的貢獻
所以這個數必須在這個序列中第一次出現,若它前面已經有一樣的數了那必然會去重,而當前這個數就是被去掉的那個,對答案就沒有貢獻了
我們開個桶 \(c\) 來記錄第 \(i\) 位前面有多少個值和它相同的數,它貢獻的答案就是 \(2^{n-c_i}\)
第二個問題:求所有連續子序列的權值和
\(l_i\)\(i\) 前面與它最後一個相同的數的位置,貢獻的答案為 \((n-i+1)*(i-l_i)\)
因為 \(a_i\) 可能很大所以需要離散化
\(code\)

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#define maxn 100010
#define int long long
#define pii pair<long long,long long>
#define fir first
#define sec second
using namespace std;
template<typename T>
inline void read(T &x){
	x=0;bool flag=0;char c=getchar();
	for(;!isdigit(c);c=getchar()) if(c=='-') flag=1;
	for(;isdigit(c);c=getchar()) x=x*10+(c^48);
	if(flag) x=-x;
}

const int mod=10007;
int n;
pii a[maxn];
int c[maxn],num[maxn],l[maxn],p[maxn],sum1,sum2,cnt;

bool cmp(pii x,pii y){
	return x.fir<y.fir;
}

signed main(){
	read(n);
	for(int i=1;i<=n;i++){
		read(a[i].fir);
		a[i].sec=i;
	}
	sort(a+1,a+n+1,cmp);
	for(int i=1;i<=n;i++){
		if(a[i-1].fir!=a[i].fir) cnt++;
		num[a[i].sec]=cnt;
	}
	p[0]=1;
	for(int i=1;i<=n;i++) p[i]=(2*p[i-1])%mod;
	for(int i=1;i<=n;i++){
		c[num[i]]++;
		(sum1+=p[n-c[num[i]]])%=mod;
		(sum2+=(n-i+1)*(i-l[num[i]])%mod)%=mod;
		l[num[i]]=i;
	}
	cout<<sum1<<" "<<sum2<<endl;
	return 0;
}
/*
3
2 3 2
//
10 9
*/

C.昊昊的故事1之昊昊的崛起

果然只會敲暴力
定義 \(d(x,y)\)\(x,y\) 兩點間路徑上最小邊權
因為求的是

\[max { \sum d(x,i) } (1 \le i \le n) \]

所以很容易想到求LCA,我們可以用樹上倍增處理
但是寫的極長還一度以為自己寫假了差點自閉
60pts程式碼:

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<bits/stdc++.h>
#define maxn 500010
//#define int long long
using namespace std;
template<typename T>
inline void read(T &x){
	x=0;bool flag=0;char c=getchar();
	for(;!isdigit(c);c=getchar()) if(c=='-') flag=1;
	for(;isdigit(c);c=getchar()) x=x*10+(c^48);
	if(flag) x=-x;
}

const int inf=0x3f3f3f3f;
int n;
int f[maxn][25],dp[maxn][25],p[maxn],dep[maxn],vis[maxn],sum,tmp,ans;
int cnt=1,head[maxn];
struct node{
	int to;
	int w;
};
vector<node> g[maxn]; 
struct edge{
	int x;
	int y;
	int w;
}e[2*maxn];

bool cmp(edge a,edge b){
	return a.w>b.w;
}

int find(int x){
	return x==p[x]?x:find(p[x]);
}

void init(){
	for(int i=0;i<=n;i++) p[i]=i;
	for(int i=0;i<=n;i++){
		for(int j=0;j<=n;j++){
			dp[i][j]=inf;
		}
	}
}

void pre(){
	int tot=0;
	for(int i=1;i<=n-1;i++){
		int x=e[i].x,y=e[i].y,w=e[i].w;
		int dx=find(x),dy=find(y);
		if(dx!=dy){
			p[dx]=dy;
			node b;
			b.to=y;
			b.w=w;
			g[x].push_back(b);
			b.to=x;
			g[y].push_back(b);
			tot++;
			if(tot>=n-1) break;
		}
	}
}

void dfs(int u,int fa,int step,int w){
	vis[u]=1;
	if(fa) f[u][0]=fa,dp[u][0]=w;
	dep[u]=step;
	int len=g[u].size();
	for(int i=0;i<len;i++){
		int to=g[u][i].to;
		if(to==fa)continue;
		dfs(to,u,step+1,g[u][i].w);
	}
}

void work(){
	for(int j=1;j<=19;j++){
		for(int i=1;i<=n;i++){
			f[i][j]=f[f[i][j-1]][j-1];
		}
	}
	for(int j=1;j<=19;j++){
		for(int i=1;i<=n;i++){
			dp[i][j]=min(dp[i][j-1],dp[f[i][j-1]][j-1]);
		}
	}
}
int lca(int x,int y){
	int res=inf;
	if(dep[x]<dep[y]) swap(x,y);
	for(int i=19;i>=0;i--){
		if(dep[f[x][i]]>=dep[y]){
			res=min(res,dp[x][i]);
			x=f[x][i];
		}
	}
	if(x==y) return res;
	for(int i=19;i>=0;i--){
		if(f[x][i]!=f[y][i]){
			res=min(res,min(dp[x][i],dp[y][i]));
			x=f[x][i],y=f[y][i];
		}
	}
	res=min(res,min(dp[x][0],dp[y][0]));
	return res;
}

int main(){
	read(n);
	for(int i=1;i<=n-1;i++)read(e[i].x),read(e[i].y),read(e[i].w);
	sort(e+1,e+n-1+1,cmp);
	init();
	pre();
	for(int i=1;i<=n;i++){
		if(!vis[i]) dfs(i,0,1,inf);
	}
	work();
	for(int i=1;i<=n;i++){
		sum=0;
		for(int j=1;j<=n;j++){
			if(i!=j){
				sum+=lca(i,j);
			}
		}
		ans=max(ans,sum);
	}
	printf("%d\n",ans);
	return 0;
}
/*
4
1 2 2
2 4 1
2 3 1
//
4
*/

正解是並查集
將邊權從大到小排序,設當前邊為 \((x,y,w)\) ,假設有兩個連通塊,左邊的最優值是 \(A\) ,右邊的最優值是 \(B\)\(x \subset A\)\(y \subset B\)。通過當前的邊使左右聯通, 最大值就是 \(max(A+siz_y*w,B+siz_x*w)\)
由此得出用並查集維護,每次合併時執行求最大值的操作,這樣合併到最後就是答案
\(code\)

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<bits/stdc++.h>
#define maxn 500010
//#define int long long
using namespace std;
template<typename T>
inline void read(T &x){
	x=0;bool flag=0;char c=getchar();
	for(;!isdigit(c);c=getchar()) if(c=='-') flag=1;
	for(;isdigit(c);c=getchar()) x=x*10+(c^48);
	if(flag) x=-x;
}

int n;
int fa[maxn],d[maxn];
struct node{
	int x;
	int y;
	int w;
}e[2*maxn];
//struct ST{
//	int x;
//	int y;
//	int w;
//	int sizx;
//	int sizy;
//}st[maxn];

bool cmp(node a,node b){
	return a.w>b.w;
}

struct bcj{
	int fa[maxn],siz[maxn];
	void init(int n){
		for(int i=1;i<=n;i++) fa[i]=i,siz[i]=1;
	}
	int find(int x){
		return (x==fa[x])?x:fa[x]=find(fa[x]);
	}
	void u(int x,int y,int w){
		x=find(x),y=find(y);
		if(x==y) return ;
		if(siz[x]>siz[y]) swap(x,y);
		d[y]=max(d[x]+siz[y]*w,d[y]+siz[x]*w);
		siz[y]+=siz[x];
		fa[x]=y;
	}
}s;

int main(){
	read(n);
	for(int i=1;i<=n-1;i++) read(e[i].x),read(e[i].y),read(e[i].w);
	s.init(n);
	sort(e+1,e+n-1+1,cmp);
	for(int i=1;i<=n-1;i++){
		s.u(e[i].x,e[i].y,e[i].w);
	}
	cout<<d[s.find(1)]<<endl;
	return 0;
}
/*
4
1 2 2
2 4 1
2 3 1
//
4
*/

D.機房的人民摸魚家

太ex了細節太多了
不會,沒寫,先咕了