Test 2022.10.09

今天是今天专场

T1 小朋友的数字

芝士一道通过率低于大多数紫题的毒瘤绿题

分析

简简单单一个最大子段和,考试的时候倒也没有想到用什么前缀和来优化,一边\(O(n)\quad dp\)一边更新答案就行,这里解释一下转移方程吧
定义:\(dp[i][2][2]\),到第\(i\)个位置,之前选了\(j\)段,当前这个点选/不选的最大子段和

\[\left\{ \begin{aligned} dp[i][0][0]=dp[i-1][0][0];当前点不选就直接继承了\\ dp[i][1][0]=max1(dp[i-1][1][1],dp[i-1][1][0]);同上,其实dp[i-1][0][1]应该是可以省略的\\ dp[i][1][1]=max1(dp[i-1][1][1],dp[i-1][0][0])+a[i];对应接上原来就有的一段、重新开辟一段 \end{aligned} \right. \]

至于为什么能够省略呢?因为这种dp方式实际上是最大\(k\)段和的特殊情况,所以我们选了\(0\)段,而当前这个却要选的情况实际上是没有意义的。

注意

然而一测交上去是只有\(85pts\)的,因为这个题数据范围是会爆\(long long\)的,但是我当时小算了下认为只会爆\(int\),所以就没有用\(int128\),后面加了\(int128\)就行了

Code

点击查看代码
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#define int __int128
using namespace std;
inline int read() 
{
	int x=0,f=1;
	char c=getchar();
	for(;!isdigit(c);c=getchar()) if(c=='-') f=-1;
	for(;isdigit(c);c=getchar()) x=(x<<3)+(x<<1)+(c^48);
	return x*=f;
}
inline void wr(int n)
{
	if(n==0) return;
	if(n<0)putchar('-'),n=-n;
    wr(n/10);
    putchar(n%10+'0');
}
inline int max1(int x,int y){return x>y?x:y;}
int abs1(int x){return x>=0?x:-x;}
const int maxn=1e6+100;
int n,p,a[maxn],dp[maxn][2][2];/*前i个选了j段第i个选或者不选的最大值*/int score[maxn];int ans=-2005120700;
signed main()
{
//	freopen("number.in","r",stdin);
//	freopen("number.out","w",stdout);
	n=read(),p=read();
	if(p==1){printf("0");return 0;}
	for(register int i=1;i<=n;++i)a[i]=read();
	int maxnum=-2005120700;
	dp[1][1][1]=a[1];;dp[1][1][0]=-2005120700;
	score[1]=dp[1][1][1];maxnum=max1(maxnum,score[1]+dp[1][1][1]);ans=max1(ans,score[1]);
	for(register int i=2;i<=n;++i)
	{
		dp[i][0][0]=dp[i-1][0][0];
		dp[i][1][0]=max1(dp[i-1][1][1],dp[i-1][1][0]);//not pick
		dp[i][1][1]=max1(dp[i-1][1][1],dp[i-1][0][0])+a[i];//picked		
		
		score[i]=maxnum;ans=max1(ans,score[i]);
		maxnum=max1(maxnum,score[i]+max1(dp[i][1][1],dp[i][1][0]));
	}
	wr(ans%p);
	return 0;
}

T2 Flood_it

题意

给出一张有初始颜色的图,每次可以改变左上角的点的颜色,那么所有和他处于同一个颜色相同连通块内的点就可以随之改变,求最少多少次能够把整张图变成同一个颜色。

分析

实际上原题并没有提到“连通块”这个词语,但是我仍然一眼看出来这道题的正解一定是搜索,没有为什么,芝士直觉,但是就是按照暴力改变原\(map\)的颜色来进行搜索,但是很容易就死循环了,因为朴素搜索里面很容易就在两个状态之间反复横跳然后就死循环了,然后名正言顺地保龄了。

正解

我们考虑有没有一种方式能够强硬的避免这种死循环,首先可以证明的是我们最多在\(n*n\)的代价内就把所有的点联通了,也就是说并不会存在无解的情况,在这种指数级增长的状态数下,我们就能够掏出我们专门求“步数问题”的\(IDA*\)了,这样既减少了复杂度,又避免了死循环一直跳下去,然后对于每一次的操作,我们都会对当前的连通块引入新的点,增加其大小,所以并不需要更改原图,只需要记录当前点是不是在连通块内就行了

IDA*

精华就在于 当前步数+预估步数>当前限制的层数的时候,我们直接\(return\)掉。那么问题就在于我们该如何设计这个估值函数,要知道我们一般给\(IDA*\)的估值一般都是尽量少,以免错过可能的答案。在这道题中,除了连通块里面的颜色,外面每有一种颜色,我们就要至少多操作一次才能得到答案,所以我们的估值函数就是除联通块外其余的颜色种数

其他的一些处理

我们定义一个\(con[][]\)数组来记录当前点是否属于连通块,或者是否是一个可以被连通块影响到的点(即连通块的边缘),分别用\(1,2\)来表示
另外,如果我们当前枚举要去填充的颜色并不能对当前图的连通性带来改变,就可以\(continue\)掉去考虑填充下一种颜色了,芝士一个极其重要的剪枝,会让代码直接快上两百毫秒左右
不想说屁话了,直接上代码吧

Code

点击查看代码
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int maxn=11;
int c1[]={-1,0,0,1},c2[]={0,-1,1,0};
int con[maxn][maxn],map[maxn][maxn],coltag[maxn];
int n,deep;
//1用来标记当前点属于左上角点的连通块,2用来标记连通块的边界可以更新的点
bool valid(int x,int y){return 1<=x&&x<=n&&1<=y&&y<=n&&con[x][y]!=1;}
void paint(int x,int y,int col)
{
	con[x][y]=1;
	for(register int i=0;i<=3;++i)
	{
		int x1=x+c1[i],y1=y+c2[i];
		if(!valid(x1,y1))continue;
		con[x1][y1]=2;
		if(map[x1][y1]==col)paint(x1,y1,col);
	}
}
int fill(int col)
{
	int ans=0;
	for(register int i=1;i<=n;++i)
		for(register int j=1;j<=n;++j)
			if(map[i][j]==col&&con[i][j]==2)ans++,paint(i,j,col);
	return ans;
}
int evaluate()
{
	int ans=0;
	memset(coltag,0,sizeof coltag);
	for(register int i=1;i<=n;++i)
		for(register int j=1;j<=n;++j)
		{
			if(coltag[map[i][j]]||con[i][j]==1)continue;
			coltag[map[i][j]]=1;
			ans++;
		}
	return ans;
}
int dfs(int step)
{
	int tmp=evaluate();
	if(step+tmp>deep)return 0;
	if(tmp==0)return 1;
	int mem[maxn][maxn];
	memcpy(mem,con,sizeof mem);
	for(register int i=0;i<=5;++i)
	{
	    if(!fill(i))continue;
		if(dfs(step+1))return 1;
		memcpy(con,mem,sizeof con);
	}
	return 0;
}
void reset(){memset(coltag,0,sizeof coltag);memset(con,0,sizeof con);}
int main()
{
	while(1)
	{
		scanf("%d",&n);if(!n)break;
		for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)scanf("%d",&map[i][j]);
		paint(1,1,map[1][1]);
        for(deep=0;deep>=0;deep++)if(dfs(0))break;
        printf("%d\n",deep);
		reset();
	}
	return 0;
}

T3 位位运算

题意

很简洁的题意,给出一个序列,我们进行若干次操作,能够把任意两个数\(x,y\)变成\(x|y,x\)&\(y\),求可以得到序列所有平方和的最大值。

分析

一言乱搞题,我们尽量把最大的数分配给一个数,一定会让这个序列的平方和变大,所以我们只用统计所数二进制下的位数,然后按贪心策略分配即可

Code

点击查看代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define int long long
using namespace std;
const int maxn=1e5;
int n,a[maxn];
int s[21];
void Sort(int x)
{
	int cnt=0;
	while(x)
	{
		s[cnt++]+=(x&1);
		x>>=1;
	}
}
signed main()
{
	scanf("%lld",&n);
	for(register int i=1;i<=n;++i)
	{
		scanf("%lld",&a[i]);
		Sort(a[i]);
	}
	long long ans=0;
	for(register int i=20;i>=0;--i)
	{
		while(s[i])
		{
			int tmp=0;
			for(register int j=i;j>=0;j--)
			{
				if(s[j])
				{
					tmp+=1<<(j);
					s[j]--;
				}
			}
			ans+=tmp*tmp;
		}
	}
	printf("%lld",ans);
	return 0;
}

T4 开车旅行

明天来改

posted @ 2022-10-09 21:23  Hanggoash  阅读(12)  评论(0编辑  收藏  举报
动态线条
动态线条end