DIV2机智题集锦

本文持续直播,CF比赛出到哪,这里就吐槽到哪,风雨无阻,绝不下播。
说明:cf官网中国大陆不太稳定,所以链接基本上都是国内镜像链接。另外,挂新加坡的VPN比俄罗斯本国的还快。提交账号均为AdaChambers(我的小小号)。

Codeforces Round #658 (Div. 2)C2.Prefix Flip (Hard Version)

题目链接

https://codeforc.es/problemset/problem/1381/A2

题意

就是想按一定的规则把01序列a变为序列b。每次选一个前缀,按位取反后翻转。完了操作次数不超过2n次。输出方案。

题解

首先发现一个有趣的性质:用i-j-i这样的三连操作,就能把i位之前的j位取反后翻转。并且保持其他不变。用这个就能线性求出easy version的结果。因为每个不相同的位花i-1-i三次操作就相同了,这样3n次就行。但是如何2n呢?这个我是强行乱搞的。我看看三个连着都不相等的,并且按照a中三位的8中可能来进行1或2次i-x-i的操作,最多6次操作就能把三位都变相同。这样三个的最多6次,平均一个2次,没有三连不同的地方就按easy versioin一位一位弄,没有三连不同的地方,最坏的是两个不同连着一个相同,所以至多有2/3n个数用单个取反的,这样不管怎样平均一个数最多2次,就乱搞过了。注意开始第1个数特判一下。

吐槽

这场太惨了。我这题早就有思路了。但是比赛摸鱼,最后急急匆匆居然没交上,排名4000+。瞬间rt暴跌,到了1600+,完全回到解放前了。

AC代码

#include<cstdio>
#include<cstring>
using namespace std;
const int NN=1e5+10;
char a[NN],b[NN];
int main(){
	int t;scanf("%d",&t);
	while(t--){
		int n;
		scanf("%d",&n);
		scanf("%s",a+1);
		scanf("%s",b+1);
		for(int i=1;i<=n;i++){
			a[i]-='0';
			b[i]-='0';
		}
		int ans=0;
		for(int i=n;i>=1;){
				if(i>=3&&a[i]!=b[i]&&a[i-1]!=b[i-1]&&a[i-2]!=b[i-2]){
					if(a[i-2]==0&&a[i-1]==0&&a[i]==0){
						ans+=3;
					}
					else if(a[i-2]==0&&a[i-1]==0&&a[i]==1){
						ans+=6;
					}
					else if(a[i-2]==0&&a[i-1]==1&&a[i]==0){
						ans+=3;
					}
					else if(a[i-2]==0&&a[i-1]==1&&a[i]==1){
						ans+=6;
					}
					else if(a[i-2]==1&&a[i-1]==0&&a[i]==0){
						ans+=6;
					}
					else if(a[i-2]==1&&a[i-1]==0&&a[i]==1){
						ans+=3;
					}
					else if(a[i-2]==1&&a[i-1]==1&&a[i]==0){
						ans+=6;
					}
					else if(a[i-2]==1&&a[i-1]==1&&a[i]==1){
						ans+=3;
					}
					i-=3;
				}
				else if(i==1&&a[i]!=b[i]){
					ans+=1;
					i--;
				}
				else if(a[i]!=b[i]){
					ans+=3;
					i--;
				}
				else i--;
		}
		printf("%d ",ans);
		for(int i=n;i>=1;){
				if(i>=3&&a[i]!=b[i]&&a[i-1]!=b[i-1]&&a[i-2]!=b[i-2]){
					if(a[i-2]==0&&a[i-1]==0&&a[i]==0){
						printf("%d %d %d ",i,3,i);
					}
					else if(a[i-2]==0&&a[i-1]==0&&a[i]==1){
						printf("%d %d %d ",i-1,2,i-1);
						printf("%d %d %d ",i,1,i);
					}
					else if(a[i-2]==0&&a[i-1]==1&&a[i]==0){
						printf("%d %d %d ",i,3,i);
					}
					else if(a[i-2]==0&&a[i-1]==1&&a[i]==1){
						printf("%d %d %d ",i-2,1,i-2);
						printf("%d %d %d ",i,2,i);
					}
					else if(a[i-2]==1&&a[i-1]==0&&a[i]==0){
						printf("%d %d %d ",i-2,1,i-2);
						printf("%d %d %d ",i,2,i);
					}
					else if(a[i-2]==1&&a[i-1]==0&&a[i]==1){
						printf("%d %d %d ",i,3,i);
					}
					else if(a[i-2]==1&&a[i-1]==1&&a[i]==0){
						printf("%d %d %d ",i-1,2,i-1);
						printf("%d %d %d ",i,1,i);
					}
					else if(a[i-2]==1&&a[i-1]==1&&a[i]==1){
						printf("%d %d %d ",i,3,i);
					}
					i-=3;
				}
				else if(i==1&&a[i]!=b[i]){
					printf("1 ");
					i--;
				}
				else if(a[i]!=b[i]){
					printf("%d %d %d ",i,1,i);
					i--;
				}
				else i--;
		}
		printf("\n");
	}
	return 0;
}

Codeforces Round #657 (Div. 2)D. New Passenger Trams

题目链接

https://codeforc.es/contest/1379/problem/D

题意

有一个火车站原本只要发火车,现在要加上客车。有一些规定:客车必须隔半个小时发一班列车,并且出于安全考虑,每趟客车发车前的k分钟上人,火车不能发车。现在可以调整第一班客车发车时间,使得能按时发车的货车尽量多。完了时间时循环的,也就是第一天的时间结束第二天从0点0分开始。

题解

这个题的第一个难点是看懂题意。很多中国同好看到题目就不想看了。

题面超级长。说白了就是蓝色的的线在圈上平移,盖住的红点最少。
我是考虑空闲时间(货车能发车的时间)能盖住的红点最多。通过略微观察发现,能留下的点要满足 ( t i − x ) % ( m / 2 ) < l e n f r e e (t_i-x)\%(m/2)<len_{free} (tix)%(m/2)<lenfree其中 t i = h i ⋅ m + m i t_i=h_i·m+m_i ti=him+mi是货车i的发车时间,以分钟时间戳记录。m是每小时多少分钟, l e n f r e e len_{free} lenfree是每个空闲的时长 l e n f r e e = m / 2 − ( k − 1 ) len_{free}=m/2-(k-1) lenfree=m/2(k1),x是第一个空闲区的起始时间,也就是第一趟客车的发车时间, 0 < = x < m / 2 0<=x<m/2 0<=x<m/2。把上面的公式化简就得到 m i % ( m / 2 ) < l e n f r e e m_i\%(m/2)<len_{free} mi%(m/2)<lenfree所以只用在乎发车分钟数而不用管小时。现在就是要找一个最小的x使得满足上式的i尽量多。这个可以贪心做。
每个货车时间可以减掉某个x,让自己满足公式。每个点的这些x可以构成一个或两个区间。 m i < l e n f r e e m_i<len_free mi<lenfree的x是2个连续区间,其他的是一个,每个点满足条件的x个数是相同的,都是 l e n f r e e len_free lenfree。这样的话把这些区间按左右端点为第一第二关键字排序,完了找到一条线穿过最多的区间就行。因为同点两个区间无交集,所以这样线穿过几个区间就是几个点。然而根据数据范围直接枚举是不行的,贪心发现排完序只用依次考虑所有区间的右端点。

  • 证明:
    • 引理:每个区间只用从上一个区间的结束点右边开始枚举,因为小于等于上个结束点的上一个区间已经枚举过了。
  • 证明,如果区间i中的某个点 p i 1 p_{i1} pi1能覆盖到下面的某个区间j的点 p j 1 p_{j1} pj1,在i区间往右k个点的 p i 2 = p i 1 + k p_{i2}=p_{i1}+k pi2=pi1+k在j上对应的点 p j 2 = p j 1 + k p_{j2}=p_{j1}+k pj2=pj1+k,这个 p j 2 p_{j2} pj2一定在在j上,因为j的左起点在i的靠后边。再加上排序和取模区间的特点,j的右端点一定在i的右边。所以 p j 2 p_{j2} pj2一定在在j上。
  • 由引理,所有i上的新美剧的扫描线都无法覆盖前面区间,所以 p i 1 p_{i1} pi1能覆盖的,右边的 p i 2 p_{i2} pi2一定能覆盖,贪心正确。

所以就是先预处理出每个货车的区间,完了排序,然后枚举区间右端点,把它在左端点集合里二分答案找到所有左端点在它左边的区间个数,就是答案。

AC代码

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int NN=100100<<2;
struct pp{
   int l,r;
}interv[NN];
int mm[NN];
bool cmp_l(pp x,pp y){
   if(x.l<y.l)return 1;
   else if(x.l==y.l&&x.r<y.r)return 1;
   return 0;
}
int n,h,m,k;
int main(){
   scanf("%d%d%d%d",&n,&h,&m,&k);
   int rest=m/2-k+1;
   int cnt=0;
   for(int i=1;i<=n;i++){
   	int x,y;
   	scanf("%d%d",&x,&y);
   	mm[i]=y;
   	int mo;
   	if(y<(m>>1) )mo=y;
   	else mo=y-(m>>1);
   	if(mo>=rest){
   		interv[++cnt].l=mo-rest+1;
   		interv[cnt].r=mo;
   	}
   	else{
   		interv[++cnt].l=0;
   		interv[cnt].r=mo;
   		interv[++cnt].l=mo+((m>>1)-rest)+1;
   		interv[cnt].r=(m>>1)-1;//0和m>>1-1是重复的
   	}
   }
   sort(interv+1,interv+cnt+1,cmp_l);
   // for(int i=1;i<=cnt;i++){
   // 	printf("*%d %d\n",interv[i].l,interv[i].r);
   // }
   int maxn=0;int x;
   for(int i=1;i<=cnt;i++){
   	pp bz;
   	bz.l=interv[i].r;
   	bz.r=m>>1;
   	int pos=upper_bound(interv+1,interv+cnt+1,bz,cmp_l)-interv;
   	if(pos-i>maxn){
   		maxn=pos-i;
   		x=interv[i].r;
   	}
   }
   printf("%d %d\n",n-maxn,x);
   for(int i=1;i<=n;i++){
   	if((mm[i]-x+m/2)%(m/2)>=rest){
   		printf("%d\n",i);
   	}
   }
   return 0;
}

Educational Codeforces Round 91D. Berserk And Fireball

题目链接

https://codeforc.es/contest/1380/problem/D

题意

有一队兵,每个兵有一个武力值。完了有两种操作:

  • 花x块钱清空k个兵
  • 花y块钱选择相邻的两个兵,武力值大的消灭武力值小的

现在想把原序列a变成b。问最小花费。如果不可能输出-1。

吐槽

CF不知道这几天咋了,各个镜像服务器接二连三遭遇大溃败,连着两场出现了令人不悦的问题,都unrated掉了。我发现我有碰EDU局了。。。

题解

这个题其实不太难。首先发现如果b不是a的子序列一定不行。完了b能把a划分成一些要删除的区间。对于每个区间只要分类讨论清楚就行:

  • 删一个好 ( y k < x ) (yk<x) (yk<x)
    • 区间有大于区间两端较大者的数,即单靠一个一个消灭是不肯消灭这个区间的,必须借助k清屏。这时又分两种:
      • 区间长度len<k,无法清屏,gg了输出-1。
      • len>=k,尽量多删一个一个的。所以删到剩k个完了一锅端。花费是 ( l e n − k ) y + x (len-k)y+x (lenk)y+x
    • 没有太大的,那就可以用两端较大者一个一个单挑。花费 l y ly ly
  • k清屏好
    • 有大与两端较大者的,并且len<k,gg输出-1
    • 否则尽量多清屏,清len/k次。完了余数用一个一个单挑。花费 l e n / k ∗ x + l e n len/k*x+len%k*y len/kx+len。就是随便找len%k次删完了直接清屏结束。

难就难在分类讨论不太好弄对。现在官方题解没出,明天看看题解怎么弄的。

AC代码

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int NN=2e5+10;
int pos[NN];
int a[NN];
int b[NN];
int f[NN];
int main(){
	int n;scanf("%d",&n);
	int m;scanf("%d",&m);
	long long x,k,y;
	scanf("%lld%lld%lld",&x,&k,&y);
	for(int i=1;i<=n;i++){
		scanf("%d",a+i);
		pos[a[i]]=i;
	}
	for(int i=1;i<=m;i++){
		scanf("%d",b+i);
		if(pos[b[i]]<=pos[b[i-1]]){
			printf("-1\n");
			return 0;
		}
		f[pos[b[i]]]=1;
	}
	f[0]=1;f[n+1]=1;
	if(y*k<x){
		long long ans=0;
		int ll,rr;
		for(int i=1;i<=n;i++){
			if(f[i]==0&&f[i-1]==1){
				ll=i;
			}
			if(f[i]==0&&f[i+1]==1){
				rr=i;
				int len=rr-ll+1;
				int ma=max(a[ll-1],a[rr+1]);
				int maxn=0;
				for(int j=ll;j<=rr;j++){
					maxn=max(maxn,a[j]);
				}
				if(maxn<ma){
					ans+=len*y;
				}
				else{
					if(len<k){
						printf("-1\n");
						return 0;
					}
					ans+=(len-k)*y+x;
				}
			}
		}
		printf("%lld\n",ans);
	}
	else{
		long long ans=0;
		int ll,rr;
		for(int i=1;i<=n;i++){
			if(f[i]==0&&f[i-1]==1){
				ll=i;
			}
			if(f[i]==0&&f[i+1]==1){
				rr=i;
				int len=rr-ll+1;
				int ma=max(a[ll-1],a[rr+1]);
				int maxn=0;
				for(int j=ll;j<=rr;j++){
					maxn=max(maxn,a[j]);
				}
				if(maxn>ma){
					if(len<k){
						printf("-1\n");
						return 0;
					}
				}
				ans+=(len/k)*x+len%k*y;
			}
		}
		printf("%lld\n",ans);
	}
	return 0;
}

Codeforces Round #655 (Div. 2)D. Omkar and Circle

题目链接

https://codeforc.es/contest/1372/problem/D

题意

有个奇数大小约瑟夫环。每次选当前环上一个数,用它相邻两个数的和替换,完了删掉相邻两数。问最后剩一个数最大是多少。

吐槽

现在div2越来越难了。我思路又假了。我想的是每次选当前环上最小的数删掉,用优先队列模拟,但这是不对的。因为删掉一个最小数,它相邻的数就合为一体,无法再删,如果相邻的数也有挺小的数,就会不优。比如下面的反例 [ 4 , 2 , 1 , 2 , 4 ] [4, 2 ,1, 2, 4] [4,2,1,2,4]这个两次都删2最优。按照先删小的只能删1和4,不是最优的。

题解

最后剩的一个数一定是一些原序列之数的和。这些数有什么性质呢?观察后发现这些数是有两个挨着的数,其他隔一个算一个的数。所以确定了挨着的两个数,其他的就定了。暴力方法就是枚举n个挨着两个数的位置,完了每次在O(n)统计其他隔一个算一个的数的和。这样 O ( n 2 ) O(n^2) O(n2)gg思密达。仔细想想发现其实把原方案转两格(奇偶性相同),最后的差别是常数级别合一算的(怎么算看代码)。而每次推进的两格,n次刚好枚举完所有点(在环上转2圈)。完了复杂度 O ( n ) O(n) O(n)

AC代码

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
const int NN=2e5+10;
int a[NN];
int main(){
	int n;scanf("%d",&n);
	for(int i=1;i<=n;i++){
		scanf("%d",a+i);
	}
	long long maxn=0;
	long long sum=0;
	for(int i=1;i<=n;i+=2){
		sum+=a[i];
	}
	maxn=max(maxn,sum);
	int lie=3;
	for(int i=2;i<=n;i++){
		int l1=lie-1,l2=lie-2;
		if(l1<=0)l1+=n;
		if(l2<=0)l2+=n;
		sum+=a[l1]-a[l2];
		maxn=max(maxn,sum);
		lie+=2;
		if(lie>n)lie-=n;
	}
	printf("%lld\n",maxn);
	return 0;
}

原来的错误代码(wrong answer on test5):

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
const int NN=2e5+10;
struct ppp{
	long long v;
	int l,r;
	int id;
}a[NN];
struct cmp{
	bool operator ()(const int &x, const int &y)
    {
        if(a[x].v>a[y].v)return 1;
        else if(a[x].v==a[y].v&&a[x].id>a[y].id)return 1;
        return 0;
    }
};
priority_queue<int ,vector<int>,cmp> q;
int f[NN];
int main(){
	int n;scanf("%d",&n);
	for(int i=1;i<=n;i++){
		scanf("%lld",&a[i].v);
		a[i].l=i-1;
		a[i].r=i+1;
		if(a[i].l==0)a[i].l=n;
		if(a[i].r==n+1)a[i].r=1;
		a[i].id=i;
		q.push(i);
	}
	for(int i=1;i<=(n>>1);i++){
		int neww;
		while(1){
			neww=q.top();
			q.pop();
			if(f[neww]==0)break;
		}
		a[neww].v=a[a[neww].l].v+a[a[neww].r].v;
		f[a[neww].l]=1;
		f[a[neww].r]=1;
		a[neww].l=a[a[neww].l].l;
		a[neww].r=a[a[neww].r].r;
		a[a[neww].l].r=neww;
		a[a[neww].r].l=neww;
		q.push(neww);
	}
	while(1){
		int neww=q.top();
		q.pop();
		if(f[neww]==0){
			printf("%lld\n",a[neww].v);
			break;
		}
	}
	return 0;
}

Educational Codeforces Round 90 (Rated for Div. 2)C.Pluses and Minuses

题目链接

https://codeforc.es/contest/1373/problem/C

题意

给了一段代码,算res最后的值。题目给的代码:

res = 0
for init = 0 to inf
    cur = init
    ok = true
    for i = 1 to |s|
        res = res + 1
        if s[i] == '+'
            cur = cur + 1
        else
            cur = cur - 1
        if cur < 0
            ok = false
            break
    if ok
        break

吐槽

这道像题外表看似大人,智商却不如小孩的名侦探毛利大叔的女儿的男朋友。但是尽管挺水,却卡了我1小时。我开场就把d过了。完了c题一直到弄的比赛快结束,结果4题滚粗。2000+名,rating掉惨了。我就应该好好准备期末考试,结果非得手贱打了这场该死的edu局。哎,活该考试前失眠。

题解

题目的代码大概是每次可以在字符串前加一些+号,完了扫描到第一次±括号不匹配出退出,比较次数一共多少次。看起来挺唬人的,仔细想想:第i次扫描res增加的量,应该是最靠前的下标j,1-j中的+比-
的数量少了i+1。定义j的前缀差为1-j中-比+多了多少。所以第i次扫描的res增量是第一个前缀差为i+1的下标。有了这个结论就多了很多YY空间:

  • 索性可以维护每种前缀差出现的第一个位置,因为每次扫描不可能到一个前缀差等价类的后面几个地方。这个直接O(n)跑个前缀差(范围最大-n到n),完了前缀差大于0(小于0的没有用,因为不可能在这里停下)的直接记第一个在一个记录数组f中。f[i]=j就表示前缀差为i第一次出现在j点。
  • 显然,第i次扫描(前面加了i个+)res的增量是 m i n ( f i + 2 , f i + 2 , . . . , f n ) min(f_{i+2},f_{i+2},...,f_n) min(fi+2,fi+2,...,fn)所以索性改f为 f i = m i n ( f i , f i + 1 ) f_i=min(f_i,f_{i+1}) fi=min(fi,fi+1)即后缀最小值。注意边界细节:f的值要初始化都为n,必须这样,为了最后一次扫描结果正确做铺垫。
  • 记录最大前缀差就是最大扫描次数+1,i从0开始,完了每次res+=f[i+1]就完了。处理f的复杂度O(n),完了累加res也是O(n)。最后还是O(n)。

AC代码

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int NN=2e6+10;
char s[NN];
int f[NN];
int main(){
	int t;
	scanf("%d",&t);
	while(t--){
		int n;
		scanf("%s",s+1);
		n=strlen(s+1);
		int sum1=0,sum0=0;
		for(int i=0;i<=n;i++)f[i]=1e9;
		int maxn=0;
		for(int i=1;i<=n;i++){
			if(s[i]=='+'){
				sum1+=1;
			}
			else {
				sum0+=1;
			}
			if(sum0-sum1>=0){
				maxn=max(maxn,sum0-sum1);
				if(f[sum0-sum1]==1e9)f[sum0-sum1]=i;
			}
		}
		f[n+1]=n;
		for(int i=n;i>=0;i--){
			f[i]=min(f[i+1],f[i]);
		}
		long long s=0;
		for(int i=0;i<=maxn;i++){
			res+=f[i+1];
		}
		printf("%lld\n",res);
	}
	return 0;
}

想请之后代码很短。我开始想的极度复杂,耽误时间了。哭了。

Educational Codeforces Round 90 (Rated for Div. 2)D.Maximum Sum on Even Positions

题目链接

https://codeforc.es/contest/1373/problem/D

题意

有一个序列下标从0开始,可以找一个区间,把他翻转。完了求翻完之后小标偶数位的最大和。

题解

我习惯从1开始,所以我就当成从1开始的奇数位之和。首先求一下不翻转的结果,想现在要转一段,使得结果增加最大。怎么办呢?暴力枚举区间的话一共 O ( n 2 ) O(n^2) O(n2)个区间,gg思密达。很容易发现长度为偶数的区间翻转答案才会有变化。然后看看具体怎么变。设 s u m 0 i sum0_i sum0i为i之前的偶数位前缀和, s u m 1 i sum1_i sum1i为奇数位前缀和。完了区间变化一定是原来的奇偶位交换,所以应该找区间原偶数位之和减区间奇数位之和最大的区间翻转。区间[i,j]这个差值为 s u m 0 j − s u m 0 i − 1 − ( s u m 1 j − s u m 1 i − 1 ) = s u m 0 j − s u m 1 j + s u m 1 i − 1 − s u m 0 i − 1 sum0_j-sum0_{i-1}-(sum1_j-sum1_{i-1})=sum0_j-sum1_j+sum1_{i-1}-sum0_{i-1} sum0jsum0i1(sum1jsum1i1)=sum0jsum1j+sum1i1sum0i1所以想让差值大,在右端点确定时要想办法快速确定左端点使得 s u m 1 i − 1 − s u m 0 i − 1 sum1_{i-1}-sum0_{i-1} sum1i1sum0i1最大。而这个东西可以在来的过程中前缀最大值。

吐槽

所以这道题比上道c题水多了。完了后面efg题有难得一匹,导致大家都是4题。完了我又被C题卡了,手速一慢,疯狂掉分。TM本来好几次都要上紫名了。完了每次都是手欠打了不该掺和的比赛,疯狂掉分。好好复习期末考试多香啊。

AC代码

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int NN=2e5+10;
long long sumo[NN],sume[NN],maxo[NN],maxe[NN];
const long long oo=100000000000000000ll; 
int a[NN];
int main(){
	int t;
	scanf("%d",&t);
	while(t--){
		int n;scanf("%d",&n);
		for(int i=1;i<=n;i++)scanf("%d",a+i);
		for(int i=1;i<=n;i++){
			if(i%2==1){
				sumo[i]=sumo[i-1]+a[i];
				sume[i]=sume[i-1];
			}
			else{
				sumo[i]=sumo[i-1];
				sume[i]=sume[i-1]+a[i];
			}
		}
		long long res=0;
		maxo[0]=-oo;
		maxe[0]=0;
		for(int i=1;i<=n;i++){
			if(i%2==1){
				maxo[i]=max(maxo[i-1],sumo[i]-sume[i]);
				maxe[i]=maxe[i-1];
				res=max(res,sume[i]-sumo[i]+maxo[i]);
			}
			else{
				maxo[i]=maxo[i-1];
				maxe[i]=max(maxe[i-1],sumo[i]-sume[i]);
				res=max(res,sume[i]-sumo[i]+maxe[i]);
			}
		}
		printf("%lld\n",sumo[n]+res);
	}
	return 0;
}

Educational Codeforces Round 89C. Palindromic Paths

题目链接

https://codeforc.es/contest/1366/problem/C

题意

题意是有个01矩阵,完了要改最小数量的格子,使得左上角到右下角的所有路径是回文路径。

吐槽

活了这么大,我总结出了世界上三个绝对不能碰的禁忌:

  • 烤了1小时鸡腿的烤箱

  • 地宫东南角的蜡烛灭掉之后,棺材里的冥器。

  • CF的EDU局

  • 头一样东西小时候总是舔,完了舌头暂时报废次数多了,现在渐渐不嘴欠了。

  • 第二样东西是书上说的,我自己也没试过,有兴趣可以趁着月黑风高去试试,但是能不能生存下来就不好说了。

  • 但是后一样,真的忍不住,屡教不改,每次都掉分,但总有上分的侥幸心理。这可能跟赌徒发誓不在碰老千,完了剁手都忍不住一样。。。

这次我又没忍住,果然死的很难看。

题解

一道c题整这么难。仔细想想就会发现满足条件的矩阵满足一些性质:矩阵中心对称(保证回文),各个主对角线上,每条对角线元素必须相同(保证同距离相同),但是回文长度为奇数的话中间一个对角线可以不变。那么就只要看看每条对角线那0多还是1多就完了。少数服从多数。

AC代码

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int NN=40;
int aa[NN][NN],a[NN][NN];
int main(){
	int t;scanf("%d",&t);
	while(t--){
		int n,m;scanf("%d%d",&n,&m);
		for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)scanf("%d",&aa[i][j]);
		if(n>m){
			for(int i=1;i<=m;i++){
				for(int j=1;j<=n;j++)a[i][j]=aa[j][i];
			}
			swap(n,m);
		}
		else{
			for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)a[i][j]=aa[i][j];
		}
		int len=n+m-1;
		int ans=0;
		if(len%2){
			for(int i=1;i<=len/2;i++){
				int up=min(i,n);
				int rec0=0,rec1=0;
				for(int j=1;j<=up;j++){
					if(a[j][i-j+1]==1)rec1++;
					else rec0++;
					if(a[n-j+1][m-i+j]==1)rec1++;
					else rec0++;
				}
				ans+=min(rec0,rec1);
			}
		}
		else{
			for(int i=1;i<=len/2;i++){
				int up=min(i,n);
				int rec0=0,rec1=0;
				for(int j=1;j<=up;j++){
					if(a[j][i-j+1]==1)rec1++;
					else rec0++;
					if(a[n-j+1][m-i+j]==1)rec1++;
					else rec0++;
				}
				ans+=min(rec0,rec1);
			}
		}
		printf("%d\n",ans);
	}
	return 0;
}

我这个代码写丑了,其实两种情况代码是一样的。

Educational Codeforces Round 89D. Two Divisors

题目链接

https://codeforc.es/contest/1366/problem/D

题意

找到a的两个因数,使得两个因数的和与原数a互素。一共2e5个a要弄。

吐槽

这道题一看就是个构造题。挺难的。我开始觉得自己找到了方法,后来被队长叉掉了。难就难在很难想到构造的方法。但是想到了就很简单了。场上不少人过掉了,但我不会。赛后群里讨论我的想法被发现是个假算法,其他人也都不太会。完了看看官方题解恍然大悟。

题解

画log的时间把a分解质因数了,如果只有一个质因数,那肯定不行。如果有多个,那么把这些质因数随便分成非空的两组,每组乘积就是答案。为什么是这样呢?

  • 证明:
    • d 1 = p 1 ⋅ p 2 ⋅ . . . ⋅ p x d 2 = p x + 1 ⋅ p x + 2 ⋅ . . . ⋅ p n d_1=p_1·p_2·...·p_x\\d_2=p_{x+1}·p_{x+2}·...·p_n d1=p1p2...pxd2=px+1px+2...pn
    • 对于任何一个质因数 p i p_i pi,它一定只被 d 1 d 2 d_1d_2 d1d2其中之一整除,所以一定不会被 d 1 + d 2 d_1+d_2 d1+d2整除。所以 d 1 + d 2 d_1+d_2 d1+d2和a不可能有公约数。
    • 如果有的话,一定是某个 p i p_i pi,而任意一个 p i p_i pi都不可能整除 d 1 + d 2 d_1+d_2 d1+d2,所以互素。

这样就很好做了。然而我居然没想到。。。

AC代码(欧拉筛板子)

#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
const int NN=1e7+10;
int prime[NN];
int visit[NN];
int minDiv[NN];
const int nn=5e5+10;
int d1[nn],d2[nn];
void Prime(int n){
    memset(visit,0,sizeof(visit));
    for (int i = 2;i <= n; i++) {
        if (!visit[i]) {
            prime[++prime[0]]=i;
            minDiv[i]=i;
        }
        for (int j = 1; j <=prime[0] && i*prime[j] <=n; j++) {
            visit[i*prime[j]] = 1;
            minDiv[i*prime[j]]=prime[j];
            if (i % prime[j] == 0) {
                break;
            }
        }
    }
}
int main(){
	Prime(1e7);
	int n;scanf("%d",&n);
	for(int i=1;i<=n;i++){
		int x;
		scanf("%d",&x);
		int xx=1;
		while(x>1){
			if(xx%minDiv[x]){
				xx*=minDiv[x];
			}
			x/=minDiv[x];
		}
		int cd1=minDiv[xx];
		int cd2=xx/cd1;
		if(cd2%cd1==0||cd1%cd2==0){
			d1[i]=-1;
			d2[i]=-1;
		}
		else{
			d1[i]=cd1;
			d2[i]=cd2;
		}
	}
	for(int i=1;i<=n;i++){
		printf("%d ",d1[i]);
	}printf("\n");
		for(int i=1;i<=n;i++){
		printf("%d ",d2[i]);
	}printf("\n");
	return 0;
}

一发入魂,爽!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值