CF1102(*^▽^*)

A.Integer Sequence Dividing

CF原题链接

题目大意:

给出n,要求把序列1,2,,n分成两个集合,输出两个集合的和的最小差值。(1n2×109)

解题思路:

我们坚信它是可以分成我们想要的两个差值最小的集合的 差值一定和Σi=1ni有关。若Σi=1ni是偶数,那么差值为零,否则必为一。

详细一点:为了抵消差值,分集合时一定是1,n一组、2,n1一组……以此类推。那么有以下几种情况:

  • 全部抵消完,差值一定为零。
  • 剩下一个数,我们将 1 提出来,将剩下的 n1 个数按照以上方式匹配,最开始的 1 即是差值。
  • 剩下两个数,将 1,2 提出来,剩下的继续分,最后差值即为 21 也就是 1
  • 剩下三个数,将 1,2,3 提出来,剩下的继续分,最后差值即为 1+23 也就是 0
  • 剩下四个数?是不会剩下四个数的

综上,差值不是 0 就是 1 。那么显然,答案就和Σi=1ni的奇偶有关了。

真·小代码
#incIude <bits/stdc++.h>
#define int long long
using namespace std;
int n;

signed main()
{
	scanf("%lld",&n);
	int sum=(n+1)*n/2;
	printf("%lld",sum%2);
	return 0;
}

B.Array K-Coloring

CF原题链接

题目大意:

给出一个长度为n的序列a,用k种颜色染色,要求:

  • 每种颜色必须用到
  • 每个元素必须被染色
  • 序列中相同的数字不能染上相同的颜色,即对于ai=aj,ij,满足colicolj

若可行,则输出染色方案。(1kn5000)

解题思路:

满足第一个要求,只需要从 1~k 反复给序列染色,每次++,超过k就归一;满足第三个要求,那么先给序列a排序,使相同的ai在同一区间内被染色(根据染色方法,相邻两个元素不会染同样的色)。根据鸽巢原理,若元素ai的数量超过k,那么一定无法染色

不好的代码
#incIude <bits/stdc++.h>
#define int long long
using namespace std;
const int N=5005;
int n,k;
struct node{
	int x,d,col;
}a[N];
int p;

bool cmp1(node x,node y) {  return x.x<y.x;  }
bool cmp2(node x,node y) {  return x.d<y.d;  }
signed main()
{
	scanf("%lld%lld",&n,&k);
	for (int i=1;i<=n;i++) 
	{
		scanf("%lld",&a[i].x);
		a[i].d=i;
	}
	sort(a+1,a+1+n,cmp1);
	
	p=1;//颜色指针
	int cnt=1;//记录相同元素的个数
	for (int i=1;i<=n;i++)
	{
		if (a[i].x==a[i-1].x) cnt++;
		else cnt=1;
		if (cnt>k) { printf("NO"); return 0; }
		a[i].col=p++;
		if (p>k) p=1;
	}
	
	sort(a+1,a+1+n,cmp2);
	printf("YES\n");
	for (int i=1;i<=n;i++) printf("%lld ",a[i].col);
	return 0;
}

C.Doors Break and Repairing

CF原题链接

题目大意:

给定一个长度为n的数列a,以下两个操作交替进行:

  1. 选择一个ai0,使得ai=max(aix , 0 )
  2. 选择一个ai0,使得ai=ai+y

执行操作一的一方想要尽可能地使更多地ai变成 0,执行操作二的一方想要尽可能地让更少的ai变成 0。求双方都采取最优策略时,最多有多少个ai0(1n100,1x,y,ai105)

解题思路:

x,y的关系进行分类讨论。

  • x>y,由于没有操作次数的限制,所以最后序列中所有数一定会归零(感性理解为入不敷出迟早要没)
  • xy,那么只有aix才有可能被清零(感性理解为一次pass掉);又因操作二采取最优策略,每次一定会选择aix进行操作,而操作完毕后ai一定无法被一次清零;所以记cnt为原序列中aix的个数,最后序列中会有cnt2个数归零。
小代码
#incIude <bits/stdc++.h>
#define int long long
using namespace std;
const int N=110;
int n,x,y;
int a[N];
int cnt;

signed main()
{
	scanf("%lld%lld%lld",&n,&x,&y);
	for (int i=1;i<=n;i++) 
	{
		scanf("%lld",&a[i]);
		if (a[i]<=x) cnt++;
	}
	
	if (x>y) printf("%lld",n);
	else printf("%lld",(cnt+1)/2);
	return 0;
}

D.Balanced Ternary String

CF原题链接

题目大意:

给出一个长度为n、仅由'0','1','2'组成的字符串s,要求改动最少的位置,使得s0,1,2的个数相同;若有多种方案,输出字典序最小的一个。(保证n3的倍数,3n3×105)

解题思路:

显然贪心。若需要改动,一定要让0尽量靠前,2尽量靠后,1最后考虑。

然后直接模拟就行了?

!代码很史,谨慎查看!
!代码很史,谨慎查看!
!代码很史,谨慎查看!

很史的代码
#incIude <bits/stdc++.h>
#define int long long
using namespace std;
const int N=3e5+5;
const int inf=0x3f3f3f3f3f3f3f3f;
int n;
char s[N];
int num;
int bx[3];//每个字符出现的个数 
vector <int> d[3];
int l[3],r[3];//指针,指向改动的最前、最后位置

signed main()
{
	scanf("%lld%s",&n,s);
	num=n/3;
	for (int i=0;i<n;i++) 
	{
		bx[(int)(s[i]-'0')]++;
		d[(int)(s[i]-'0')].push_back(i);
	}
	r[0]=bx[0]-1,r[1]=bx[1]-1,r[2]=bx[2]-1;
	while (bx[0]<num)
	{
		int mn=inf;//0尽量靠前
		if (bx[1]>num) mn=min(mn,d[1][l[1]]);
		if (bx[2]>num) mn=min(mn,d[2][l[2]]);
		bx[0]++;
		s[mn]='0';
		if (bx[1]>num&&mn==d[1][l[1]]) l[1]++,bx[1]--;
		else l[2]++,bx[2]--;
	}
	while (bx[2]<num)
	{
		int mx=0;//2尽量靠后
		if (bx[1]>num) mx=max(mx,d[1][r[1]]);
		if (bx[0]>num) mx=max(mx,d[0][r[0]]);
		bx[2]++;
		s[mx]='2';
		if (bx[1]>num&&mx==d[1][r[1]]) r[1]--,bx[1]--;
		else r[0]--,bx[0]--;
	}
	while (bx[1]<num)
	{
		int dd;
		if (bx[0]>num) dd=d[0][r[0]];//1最后看情况
		else dd=d[2][l[2]];
		bx[1]++;
		s[dd]='1';
		if (bx[0]>num) r[0]--,bx[0]--;
		else l[2]++,bx[2]--;
	}
	printf("%s",s);
	return 0;
//都看到这了,为蒟蒻的代码提提建议吧,总感觉这样写太废太冗杂了……
}

E.Monotonic Renumeration

CF原题链接

题目大意:

给出一个长度为n的序列a,需构造一个单调不降的序列b,满足:

  1. b1=0
  2. 对于i[2,n]bi=bi1bi=bi1+1
  3. 对于任意一组i,j,若满足ai=aj,那么必须bi=bj

要求输出构造b序列的方案数,对998244353取模(2n2×105,1ai109)

解题思路:

小清新计数题。

注意到条件三的限制,我们手模后发现对于满足ai=aj的区间[i,j],应也满足bi=bi+1==bj,不然无法保证b序列单调不降。序列a中会形成若干个以上描述的区间,若几个区间内有交,那么可以看作一个大区间(虽然大区间的两个端点i,j可能不满足ai=aj,但仍需满足大区间内b中元素相等)。

对于大区间[i,j],必然满足bi=bi+1==bj。对于每个大区间i,它有两种取值;于是统计大区间的个数cnt,方案数就是2cnt1。(因为第一个区间,也就是b1所在的区间取值是确定的)

可结合图片食用

um有点难评……那么,结合代码食用

可爱的小代码
#incIude <bits/stdc++.h>
#define int long long
using namespace std;
const int N=2e5+5;
const int MOD=998244353;
int n;
int a[N];
map <int,int> mx,mn;//可恶的数据范围 不能用数组存
int tol;
struct node{
	int l,r;//记录对于相同元素a[i]所形成的最大区间
}st[N];

bool cmp(node x,node y) {  return x.l<y.l;  }
int qsm(int a,int b)//注意到数据范围,选择快速幂
{
	int res=1;
	while (b)
	{
		if (b&1) res=(res*a)%MOD;
		a=(a*a)%MOD;
		b>>=1;
	}
	return res;
}
signed main()
{
	scanf("%lld",&n);
	for (int i=1;i<=n;i++) scanf("%lld",&a[i]);
	
	for (int i=1;i<=n;i++) mx[a[i]]=i;//统计每个数出现的最初、最后位置
	for (int i=n;i>=1;i--) mn[a[i]]=i;
	for (int i=1;i<=n;i++) st[i]={mn[a[i]],mx[a[i]]};//记录a[i]所在的第一种区间
	
	sort(st+1,st+1+n,cmp);
	int r=st[1].r;
	for (int i=2;i<=n;i++)//统计不交区间个数
	{
		if (st[i].l>r) tol++;
		r=max(r,st[i].r);
	}
	printf("%lld",qsm(2,tol));
	return 0;
} 

F.Elongated Matrix

CF原题链接

题目大意:

给定一个nm列的矩阵ai,j,你可以改变每一行的顺序,但不能改变行内元素的位置。

确定矩形后,你可以通过以下顺序遍历整个矩阵:首先从顶部到底部遍历矩阵第一列,然后对第二列进行相同操作,以此类推。在遍历期间,按照遍历顺序记录序列s,记为s1,s2,s3,,snm

我们称一个k是合法的,当且仅当对于i[2,nm],满足|sisi1|k

要求对于给出矩阵,合法的k值最大。(1n16,1m104,2nm)

解题思路:

看到数据范围,果断想到状压dp 但是我还是不会QwQ

注意到时限为4s。嗯4s。4s?4s!

设状态fi,j表示已选了i集合的行、以j行结尾的最大k值。状态转移方程即

fS  j, j=max{min(fS,i,gi,j)}

其中gi,j表示第i,j行相邻时的最小差值,暂时不考虑第一行与最后一行的差值

那么统计答案时,因为最后一行不确定,所以枚举最后一行的位置统计,即

Ans=max{min(f(1<<n)1,i,hk,i)}

其中hk,i表示第k行作为第一行、第i行作为最后一行时两行的最小差值。

复杂度O(2nn3)

不做评价的代码
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N=20;
const int M=1e4+5;
const int inf=0x3f3f3f3f3f3f3f3f;
int n,m;
int a[N][M];
int f[1<<N][N],g[N][N],h[N][N];
int ans;

signed main()
{
	scanf("%lld%lld",&n,&m);
	for (int i=0;i<n;i++)
	{
		for (int j=1;j<=m;j++) scanf("%lld",&a[i][j]);
	}
	
	//预处理 
	for (int i=0;i<n;i++)//i,j分别枚举行 
	{
		for (int j=0;j<n;j++)
		{
			g[i][j]=h[i][j]=inf;
			for (int k=1;k<=m;k++) g[i][j]=min(g[i][j],abs(a[i][k]-a[j][k]));//枚举第二维坐标 
			for (int k=2;k<=m;k++) h[i][j]=min(h[i][j],abs(a[i][k-1]-a[j][k]));
		}
	}
	
	for (int k=0;k<n;k++)//枚举第一行 
	{
		memset(f,0,sizeof f);
		f[1<<k][k]=inf;
		for (int s=0;s<(1<<n);s++)//枚举行的集合 
		{
			for (int i=0;i<n;i++) 
			{
				if (!(s&(1<<i))) continue;
				for (int j=0;j<n;j++)
				{
					if (!(s&(1<<j))) f[s|(1<<j)][j]=max(f[s|(1<<j)][j],min(f[s][i],g[i][j]));
				}
			}
		}
		for (int i=0;i<n;i++) ans=max(ans,min(f[(1<<n)-1][i],h[k][i]));//枚举最后一行并统计答案 
	}
	printf("%lld",ans);
	return 0;
}

完结撒花

posted @   还是沄沄沄  阅读(65)  评论(1编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示