24.9.1(康托展开)

上星期三:

补 24牛客多校 二 C                                                  牛客传送门

思路: 赛时写模拟写的很臭,如果用dp写就很方便

代码如下:

const int N=2e6+10;
const int mod=1e9+7;
ll n;
char s[N][2];
int dp[N][2];
void solve(){
	cin >> n;
	for(int i=1;i<=n;i++) cin >> s[i][0];
	for(int i=1;i<=n;i++) cin >> s[i][1];
	if(s[1][0]=='R') dp[1][0]=(s[1][0]=='R')+(s[1][1]=='R');
	if(s[1][1]=='R') dp[1][1]=(s[1][1]=='R')+(s[1][0]=='R');
	int ans=max(dp[1][0],dp[1][1]);
	for(int i=2;i<=n;i++){
		dp[i][0]=(s[i][0]=='R');
		dp[i][1]=(s[i][1]=='R');
		ans=max(dp[i][0]+dp[i][1],ans);
		if(s[i][0]=='R' && s[i-1][0]=='R')
			dp[i][0]=max(dp[i-1][0]+1,dp[i][0]),ans=max(dp[i][0],ans);
		if(s[i][1]=='R' && s[i-1][1]=='R')
			dp[i][1]=max(dp[i-1][1]+1,dp[i][1]),ans=max(dp[i][1],ans);
		int dp0=dp[i][0],dp1=dp[i][1];
//		if(s[i][1]=='R' && s[i-1][0]=='R' && s[i][0]=='R')
//			dp[i][1]=max(dp0+1,dp[i][1]),ans=max(dp[i][1],ans);
//		if(s[i][0]=='R' && s[i-1][1]=='R' && s[i][1]=='R')
//			dp[i][0]=max(dp1+1,dp[i][0]),ans=max(dp[i][0],ans); //不该判上一列
		if(s[i][0]=='R' && s[i][1]=='R'){
			dp[i][0]=max(dp1+1,dp[i][0]);
			dp[i][1]=max(dp0+1,dp[i][1]);
			ans=max({dp[i][0],dp[i][1],ans});
		}
	}
	if(ans) cout << ans-1 << "\n";
	else cout << 0;
}

因为上星期的题量太少,没咋写周记,就给并掉了

星期二:

补 24牛客多校二 B                                             牛客传送门

题意:给一图和q次询问,每次询问给一图内点集,问子图的最小生成树

题解pdf

思路:最小生成树用kk算法处理,如何取边采用根号分治,若 k <= \sqrt{n},双重循环枚举点集,取出存在的边,若 k > \sqrt{n},枚举 m取出有效边

然后跑克鲁斯卡尔。因为 k的sum是有限制的,所以采用对应的枚举方法可有效降低总体复杂度

代码如下:

const int N=2e5+10,M=210;
const int INF=0x3f3f3f3f;
const int mod=998244353;
ll n;
struct edge{
	int u,v,w;
	bool operator <(const edge &b)const{
		return w<b.w;
	}
}e[N];
map<PII,int>mp; 
int fa[N];
int fnd(int x){
	return fa[x]==x?x:fa[x]=fnd(fa[x]);
}
void solve(){
	int m,q; cin >> n >> m >> q;
	int sq=sqrt(n);
	for(int i=1;i<=m;i++){
		int u,v,w; cin >> u >> v >> w;
		e[i]={u,v,w};
		mp[{u,v}]=w;
		mp[{v,u}]=w;
	}
	sort(e+1,e+m+1);
	while(q--){
		int k; cin >> k;
		vector<int>ve;
		vector<edge>ed;
		unordered_map<int,bool>vi;
		for(int i=1;i<=k;i++){
			int s; cin >> s;
			ve.push_back(s);
			vi[s]=1;
			fa[s]=s;
		}
		if(k<=sq){
			for(int i=0;i<k;i++){
				for(int j=i+1;j<k;j++) if(mp.count({ve[i],ve[j]}))
					ed.push_back({ve[i],ve[j],mp[{ve[i],ve[j]}]});
			}
			sort(ed.begin(),ed.end());
			ll cnt=0,sum=0;
			for(auto t:ed){
				int u=fnd(t.u),v=fnd(t.v);
				if(u==v) continue;
				fa[u]=v;
				sum+=t.w;
				if(++cnt==k-1) break;
			}
			if(cnt==k-1) cout << sum << "\n";
			else cout << "-1\n";
		}else{
			ll cnt=0,sum=0;
			for(int i=1;i<=m;i++) if(vi.count(e[i].u) && vi.count(e[i].v)){
				int u=fnd(e[i].u),v=fnd(e[i].v);
				if(u==v) continue;
				fa[u]=v;
				sum+=e[i].w;
				if(++cnt==k-1) break;
			}
			if(cnt==k-1) cout << sum << "\n";
			else cout << "-1\n";
		}
	}
}

补 24牛客多校 二 I                                          牛客传送门

思路:p【num】【0/1】表示数字 num的左右区间下标

         dp【num】【i】表示数字为num,考虑到第 i个数的最大贡献(i <= p【num】【1】

处理大区间时,需考虑其间的小区间贡献,将区间按长度从小到大排序,对每个区间进行遍历,若遇到包含在内的小区间,对是否转移取max,O(n^2)的复杂度

代码如下:

const int N=2e5+10,M=210;
const int INF=0x3f3f3f3f;
const int mod=998244353;
ll n;
int a[3030*2],p[3030][2];
int id[3030];
ll dp[3030][3030*2];
void solve(){
	cin >> n;
	for(int i=0;i<=n;i++) id[i]=i;
	a[1]=0,p[0][0]=1;
	for(int i=2;i<=n*2+1;i++){
		cin >> a[i];
		!p[a[i]][0]?p[a[i]][0]=i:p[a[i]][1]=i;
	}
	a[n*2+2]=0,p[0][1]=n*2+2;
	n++;
	sort(id,id+n,[&](int a,int b){
		return p[a][1]-p[a][0]<p[b][1]-p[b][0];
	});
//	for(int i=1;i<=n;i++) cout << a[i] << " \n"[i==n];  //注意下标是到n*2
	for(int num=0;num<n;num++){
		int x=id[num];
		dp[x][p[x][0]]=x;
		for(int i=p[x][0]+1;i<=p[x][1];i++){
			dp[x][i]=dp[x][i-1]+x;        //默认贡献为x
			if(p[a[i]][0]>p[x][0] && i==p[a[i]][1])
				dp[x][i]=max(dp[x][p[a[i]][0]-1]+dp[a[i]][i],dp[x][i]);
		}
	}
	cout << dp[0][n*2];
}

星期三:

学了下康托展开,可用来求全排列的编号,树状数组优化后复杂度为O(nlogn)

逆康托可以把编号转为全排列(编号的数据范围需在 20!内,21!会爆 ull

学这个的目的主要是掌握逆康托,如果遇到了可暴力全排列的题且不知正解,可以试着用随机数加上逆康托,生成一个随机的全排列,然后开始暴力枚举一定次数,有极小概率撞上答案,不过此为走投无路之举,且有很大的局限性,慎用

贴个n^2板子:(附随机数范围函数

const int N=2e6+10,M=210;
const int INF=0x3f3f3f3f;
const int mod=998244353;
ll n;
ull fac[N];
ll rng(ll l,ll r){
	return rand()%(r-l+1)+l;   //指定随机数生成范围
}
ll kt(vector<int> ve){
	ll res=0;
	int sz=ve.size();
	unordered_map<int,bool>vi;
	for(int i=0;i<sz;i++){
		int cnt=0;
		for(int j=1;j<ve[i];j++) if(!vi.count(j)) cnt++;   //可用树状数组优化
		(res+=1ll*cnt*fac[sz-i-1]%mod)%=mod;
		vi[ve[i]]=1;
	}
	return ++res%mod;
}
vector<int> rev_kt(ull k,int len){             //编号和排列长度
	vector<int>ans;
	k--;
	vector<int>ve;
	for(int i=1;i<=len;i++) ve.push_back(i);
	for(int i=1;i<=len;i++){
		int t=k/fac[len-i];
		ans.push_back(ve[t]);
		ve.erase(ve.begin()+t);
		k%=fac[len-i];
	}
	return ans;
}
void solve(){
	cin >> n;
	fac[0]=1;
	for(int i=1;i<=n;i++) fac[i]=fac[i-1]*i%mod;
	vector<int>ve;
	for(int i=1;i<=n;i++){
		int x; cin >> x;
		ve.push_back(x);
	}
	cout << kt(ve);
}

星期四:

补 24牛客多校 二 G                                            牛客传送门

题意:定义一集合为好集合,需满足元素乘积为平方数即 x^2,此集合权值即为 x。给一数集,问所有为好集合的子集的权值和是多少

贴个dalao题解:2024牛客暑期多校训练营2 - Luckyblock - 博客园 (cnblogs.com)

思路:由平方数考虑到质因子分解,值域1000以内,若存在大于31的质因子,则必定只有一个,且31以内的质数也只有11个,可以状压处理,将所有数分解,若不存在大质数则分解后为1,若存在即为大质数,按此分组,对每组进行转移

如何分解:对于每个数,我们要知道它的小质数的奇偶状态,但除此之外,还要处理出它的偶数个质因子对答案的贡献,即处理为pair值,再分组处理

dp【i】【mask】【0/1】表示考虑到每组第 i个数,小质数奇偶状态为mask,选了 偶/奇个大质数

对于不存在大质数的数,只需关注小质数奇偶状态,第三维暂时用不上,对于每个【ma,v】, 枚举状态进行转移,注意答案除了需乘v外,还需乘上两状态同奇的质数

对于存在大质数的数,按大质数 p分组进行转移,因为答案不断累加,到了下一组后,之前的答案仍保留,不过需要清除选了奇数个p‘(p’<p 的状态的值。接下来仍是枚举状态转移,注意每当 p选了偶数个,dp【now^1】【mask】【0】就需乘上 p

代码如下:

const int N=2e6+10,M=210;
const int INF=0x3f3f3f3f;
const int mod=1e9+7;
ll n;
ll dp[2][1<<11][2];
vector<PII>ve[1010];
int p[11]={2,3,5,7,11,13,17,19,23,29,31};
ll cal(int ma1,int ma2){
	ll res=1;
	for(int i=0;i<11;i++) if((ma1&ma2)&1<<i) (res*=p[i])%=mod;
	return res;
}
void solve(){
	cin >> n;
	for(int i=1;i<=n;i++){
		int x; cin >> x;
		ll xma=0,v=1;
		for(int j=0;j<11;j++){
			while(x%p[j]==0){
				if(xma&1<<j) v*=p[j];
				xma^=(1<<j);
				x/=p[j];
			}
		}
		ve[x].push_back({xma,v});
	}
	int now=0;
	dp[0][0][0]=1;
	for(auto [ma,v]:ve[1]){
		for(int mask=0;mask<1<<11;mask++) dp[now^1][mask][0]=dp[now][mask][0];
		for(int mask=0;mask<1<<11;mask++){
			int nmask=mask^ma;
			(dp[now^1][nmask][0]+=dp[now][mask][0]*v%mod*cal(mask,ma)%mod)%=mod;
		}
		now^=1;
	}
	for(int val=32;val<=1000;val++) if(!ve[val].empty()){
		for(int mask=0;mask<1<<11;mask++) dp[now][mask][1]=0;
		for(auto [ma,v]:ve[val]){
			for(int mask=0;mask<1<<11;mask++)
				dp[now^1][mask][1]=dp[now][mask][1],dp[now^1][mask][0]=dp[now][mask][0];
			for(int mask=0;mask<1<<11;mask++){
				int nmask=mask^ma;
				(dp[now^1][nmask][0]+=dp[now][mask][1]*v%mod*cal(mask,ma)%mod*val%mod)%=mod;
				(dp[now^1][nmask][1]+=dp[now][mask][0]*v%mod*cal(mask,ma)%mod)%=mod;
			}
			now^=1;
		}
	}
	cout << (dp[now][0][0]-1+mod)%mod;
}

星期五:

补 24牛客多校 三 A                                          牛客传送门

思路:最少的需要从右往左的趟数为 s= ( n-r ) / ( r-l )向上取整,最后一趟载 r个人,每个来回能有 r-l个人过河。把每个人的体力数处理为能趟一来回的次数 a即a = ( h-1 )/2。

最少要往回趟 s次,每次载 l个人,在贪心即每次运输体力最高的人的前提下,所有人 a的总和最少需要 s*l,若一个人的 a大于 s,那么超出的部分没有意义,所以计算总和时 a需要和 s取min,再和 s*l进行比较

代码如下:

const int N=2e6+10,M=210;
const int INF=0x3f3f3f3f;
const int mod=1e9+7;
ll n;
ll a[N];
void solve(){
	int l,r; cin >> n >> l >> r;
	ll s=(n-r+r-l-1)/(r-l),cp=0;
	for(int i=1;i<=n;i++){
		int h; cin >> h;
		a[i]=(h-1)/2;
		cp+=min(a[i],s);
	}
	if(cp>=s*l) cout << "YES";
	else cout << "NO";
}

恐怖故事,摸了两天半鱼,发现已经星期一了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值