2021.12.4总结

2 篇文章 0 订阅
2 篇文章 0 订阅
本文作者分享了参加算法竞赛时遇到的四道题目,包括动态规划问题、序列性质探讨、跳跃游戏策略以及序列覆盖问题。针对每道题目,作者详细解析了解题思路和关键算法,涉及DP状态设计、序列预处理、暴力求解优化及单调序列处理等技巧。文章旨在帮助读者提升算法思维和解题能力。
摘要由CSDN通过智能技术生成

2021.12.4 B组 总结


赛时:
T1:试过直接找性质,但看不出;试过打表,但没耐心;
T2: 想到DP,但不知道怎么设状态和转移
T3: 只是粗看,没有细想(在讲题的时候才发现看错条件,TT)
T4: 没时间去想了(可能是对最后一道题的不自信,以及时间确实不够了,一般不做最后一题

题解:

T1:

考虑n的序列: n S(n) S(S(n)) …… 2

根据S(n)的定义可知 ∀ 整 数 i ∈ [ 1 , S ( n ) − 1 ] , i ∣ n \forall 整数i\in[1,S(n)-1],i|n i[1,S(n)1],in, 即 l c m ( 1 , 2 , … , S ( n ) − 1 ) ∣ n lcm(1,2,\ldots,S(n)-1)|n lcm(1,2,,S(n)1)n,且 S ( n ) ∤ n S(n)\nmid n S(n)n

想到令 n = l c m ( 1 , 2 , … , S ( n ) − 1 ) ∗ t , 则 ( S ( n ) / g c d ( l c m ( 1 , 2 , … , S ( n ) − 1 ) , S ( n ) ) ) ∤ t n=lcm(1,2,\ldots,S(n)-1)*t,则(S(n)/gcd(lcm(1,2,\dots,S(n)-1),S(n)))\nmid t n=lcm(1,2,,S(n)1)t,(S(n)/gcd(lcm(1,2,,S(n)1),S(n)))t

发现由于 n < = 1 0 17 , n<=10^{17}, n<=1017,所以S(n)最大为41(自己算一下)。因此,考虑直接预处理出1~41的S(n),

然后计数S(n)相同数的个数。容易发现,不会算重,直接计数即可。


T2:

​ 这篇博客不错

​ 补充其中一些细节:

在这里插入图片描述

​ 设i 位置上的数为a[i], 前半块和后半块的大小均为p

​ 对于这样的情况,整个块中数的大小一定 ∈ [ p ∗ 2 k + 1 , p ∗ ( 2 k + 2 ) ] , k ∈ Z ∗ \in[p*2k+1,p*(2k+2)],k\in Z^* [p2k+1,p(2k+2)],kZ,又因为每块中数字要连续,

​ 所以 1 当 a [ i ] ∈ [ p ∗ 2 k + 1 , p ∗ ( 2 k + 1 ) ] a[i]\in [p*2k+1,p*(2k+1)] a[i][p2k+1,p(2k+1)]时, a [ i − 1 ] ∈ [ p ∗ ( 2 k + 1 ) + 1 , p ∗ ( 2 k + 2 ) ] a[i-1]\in [p*(2k+1)+1,p*(2k+2)] a[i1][p(2k+1)+1,p(2k+2)]

​ 2 当 a [ i − 1 ] ∈ [ p ∗ 2 k + 1 , p ∗ ( 2 k + 1 ) ] a[i-1]\in [p*2k+1,p*(2k+1)] a[i1][p2k+1,p(2k+1)]时, a [ i ] ∈ [ p ∗ ( 2 k + 1 ) + 1 , p ∗ ( 2 k + 2 ) ] a[i]\in [p*(2k+1)+1,p*(2k+2)] a[i][p(2k+1)+1,p(2k+2)]

​ 即 令

l=a[i]/p+(a[i]%p?0:1); if(l&1) l++; else l--;

a [ i − 1 ] ∈ [ p ∗ ( l − 1 ) + 1 , p ∗ l ] a[i-1]\in [p*(l-1)+1,p*l] a[i1][p(l1)+1,pl]

​ p的取法很有深意,这样取可以使得刚好满足上述情况


T3:

暴力题,主要问题在于实现(也可以用tarjan缩点,从k出发求DAG的最长路,然而我不会

看样例,发现2种情况

1.经过蹦床

蹦床覆盖范围的两种情况:

在这里插入图片描述

因为可以从蹦床出发可以跳到任意点,所以如果经过蹦床,答案即为所有蹦床范围和+最大的剩余的点的连续不降或不升子序列长度

2.不经过

答案为从k出发最长合法序列长度

实现:考虑分开经过蹦床的和不经过蹦床的


T4

​ 发现,独立集对应的条件是: 选的序列单调递增(这样,集合中就不存在逆序对,即集合内的点不存在连边)

​ 覆盖集条件: 设 选 的 点 集 为 V , a n 为 原 序 列 , 则 ∀ k ∈ ( i , j ) , i , j ∈ V , i < j , 满 足 a k < a i 或 a k > a j 设选的点集为V,a_n为原序列,则\forall k\in(i,j),i,j\in V,i<j,满足a_k<a_i或a_k>a_j V,an,k(i,j),i,jVi<jak<aiak>aj

​ 由于序列单调递增,所以只需要从 i-1 到 j ,维护 比a[i]小的a[k] 的最大值<a[j] 即可

​ 对于第一个和最后一个数,可以通过强制选a[0]=0,a[n+1]=n+1,来简化转移;

​ 现在问题变成了求原序列。

​ 可以得到的信息有,与 i 位置上的数构成逆序对的数的个数(即点的度)

​ 显然,逆序对来自于[1,i-1]和[i+1,n],两个同时考虑比较麻烦,有没有办法使得枚举时只要考虑一边

​ 想到枚举时按从小到大或从大到小,可以只考虑一边,一下由从小到大考虑

​ i 位于未选择的位置的第 度数+1 个位置

​ 至于为什么对任意一个合法序列进行转移都可以得到正确答案,应该是这些序列本质上都是同一个图

​ O(n2)


代码:


T1:
#include<cstdio>
#include<cstring>
#define ull unsigned long long 
using namespace std;
ull A,B,k,sum1,sum2,f[50];
ull gcd(ull a,ull b){
	return b?gcd(b,a%b):a;
}
ull work(ull a){
	for(int i=2;i<a;i++)
		if(a%i){
			f[a]=f[i]+1;
			break;
		}
}
int main(){
	scanf("%llu%llu",&A,&B);
	k=1;A--;
	f[2]=1;
	for(ull i=2;i<=41;i++){
		work(i);
		ull cnt=A/k,d=gcd(k,i);
		cnt=cnt-cnt/(i/d);sum2+=cnt*(f[i]+1);
		cnt=B/k;cnt=cnt-cnt/(i/d);sum1+=cnt*(f[i]+1);
		k=k*i/d;
//		printf("%llu\n",k);
	}
	printf("%llu",sum1-sum2);
	return 0;
} 

T2:

#include<cstdio>
#include<cstring>
using namespace std;
const int N=1024;
int k,v[N+5][N+5],f[N+5][N+5],ans=0x3f3f3f3f;
int min(int a,int b){
	return a>b?b:a;
}
int main(){
	memset(f,0x3f,sizeof f);
	scanf("%d",&k);
	for(int i=1;i<=(1<<k);i++)
		for(int j=1;j<=(1<<k);j++)
			scanf("%d",&v[i][j]);
	for(int i=1;i<=(1<<k);i++)	f[1][i]=0;
	for(int i=2;i<=(1<<k);i++){
		int p=k;
		while(1 && p>0){
			if((i-1)%(1<<p)==0 && i%(1<<p))
				break;
			p--;
		}
		for(int j=1;j<=(1<<k);j++){
			int h=(j%(1<<p)?1:0)+(j>>p),l;
			if(h&1)	l=(1<<p)*(h+1);
			else
				l=(1<<p)*(h-1);
			for(int o=1;o<=(1<<p);o++){
				f[i][j]=min(f[i][j],f[i-1][l]+v[j][l]);
				l--;	if(i==1<<k)	ans=ans>f[i][j]?f[i][j]:ans;
			}
		}
	}
	printf("%d",ans);
	return 0;
} 

T3:

#include<cstdio>
using namespace std;
const int N=300000;
int n,k,h[N+5],cf[N+5],p=0,sum=0,cnt=0,tot=1,vis[N+5],mx;//x : 0 单调不降 
int max(int a,int b){
	return a>b?a:b;
}
int main(){
	scanf("%d%d",&n,&k);
	for(int i=1;i<=n;i++) scanf("%d",&h[i]);
	for(int i=1;i<=n;i++){
		char a;scanf(" %c",&a);
		if(a=='T'){
			int l=i-1,r=i+1;
			while(h[l]>=h[l+1] && l>=1)
				l--;
			while(h[r]>=h[r-1] && r<=n)	
			 	r++;
			if(l+1<=k && r-1>=k)	p=1;
			cf[l+1]+=1;cf[r]-=1;
		}
	}
	int bj=0;h[0]=0x3f3f3f3f;tot=0;
	for(int i=1;i<=n;i++){//单调不降
		cnt=1;bj+=cf[i]; 
		if(bj){
			mx=max(tot,mx);
			while(bj && i<=n){
				i++;
				bj+=cf[i];
			}
			if(i>n)	break;
			tot=1;i++;
		}
		if(h[i]>=h[i-1])	tot++;
		else{
			mx=max(tot,mx);
			tot=1;
		}
	}
	mx=max(tot,mx);
	bj=0;h[0]=0x3f3f3f3f;tot=0;
	for(int i=1;i<=n;i++){//单调不升 
		cnt=1;bj+=cf[i]; 
		if(bj){
			mx=max(tot,mx);
			while(bj && i<=n){
				i++;
				bj+=cf[i];
			}
			if(i>n)	break;
			tot=1;i++;
		}
		if(h[i]<=h[i-1])	tot++;
		else{
			mx=max(tot,mx);
			tot=1;
		}
	}
	mx=max(tot,mx);
	if(p){
		int s=0;
		for(int i=1;i<=n;i++){
			s+=cf[i];
			sum+=s?1:0;
		}
//		printf("%d %d ",mx,sum);
		printf("%d",mx+sum);
	}
	else{
		mx=0;tot=0;int l=k-1,r=k+1;cnt=1;
		while(h[l]==h[l+1]){
			l--;cnt++;
		}
		while(h[r]==h[r-1]){
			r++;cnt++;
		}
		while(h[l]<=h[l+1] && l>=1){	
			tot++;l--;
		}
		mx=tot+cnt;tot=0;
		while(h[r]<=h[r-1] && r<=n){
			tot++;r++;
		}
		mx=max(mx,tot+cnt);
		printf("%d",mx); 
	}
	return 0;
} 

T4:
#include<cstdio>
using namespace std;
const int M=1000000007;
const int N=1000;
int n,m,vis[N+5][N+5],du[N+5],xl[N+5],f[N+5];
int max(int a,int b){
	return a>b?a:b;
}
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++){
		int a,b;
		scanf("%d%d",&a,&b);
		du[a]++;du[b]++;
		vis[a][b]=1;vis[b][a]=1;
	}
	for(int i=1;i<=n;i++){//构造原序列 
		int cnt=0;
		for(int j=1;j<=n;j++){
			if(xl[j]) continue;
			if(cnt>=du[i]){
				xl[j]=i;
				break;	
			}
			cnt++;
 		}
		for(int j=1;j<=n;j++){
			if(j==i) continue;
			if(vis[i][j])	du[j]--;
		}
	}
	xl[0]=0;xl[n+1]=0x3f3f3f3f;f[0]=1;int mx;
	for(int i=1;i<=n+1;i++){
		mx=0;
		for(int j=i-1;j>=0;j--){
			if(mx<=xl[j] && xl[j]<xl[i])
				f[i]=(f[i]+f[j])%M;
			if(xl[i]>xl[j])
				mx=max(mx,xl[j]);
		}
	}
	printf("%d",f[n+1]);
	return 0;
} 

若有错误欢迎指出

禁止转载

12.7

update:

12.9 补充T2和T4

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值