本文持续直播,CF比赛出到哪,这里就吐槽到哪,风雨无阻,绝不下播。
说明:cf官网中国大陆不太稳定,所以链接基本上都是国内镜像链接。另外,挂新加坡的VPN比俄罗斯本国的还快。提交账号均为AdaChambers(我的小小号)。
文章目录
- Codeforces Round #658 (Div. 2)C2.Prefix Flip (Hard Version)
- Codeforces Round #657 (Div. 2)D. New Passenger Trams
- Educational Codeforces Round 91D. Berserk And Fireball
- Codeforces Round #655 (Div. 2)D. Omkar and Circle
- Educational Codeforces Round 90 (Rated for Div. 2)C.Pluses and Minuses
- Educational Codeforces Round 90 (Rated for Div. 2)D.Maximum Sum on Even Positions
- Educational Codeforces Round 89C. Palindromic Paths
- Educational Codeforces Round 89D. Two Divisors
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}
(ti−x)%(m/2)<lenfree其中
t
i
=
h
i
⋅
m
+
m
i
t_i=h_i·m+m_i
ti=hi⋅m+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−(k−1),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 (len−k)y+x。
- 没有太大的,那就可以用两端较大者一个一个单挑。花费 l y ly ly
- 区间有大于区间两端较大者的数,即单靠一个一个消灭是不肯消灭这个区间的,必须借助k清屏。这时又分两种:
- k清屏好
- 有大与两端较大者的,并且len<k,gg输出-1
- 否则尽量多清屏,清len/k次。完了余数用一个一个单挑。花费 l e n / k ∗ x + l e n len/k*x+len%k*y len/k∗x+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} sum0j−sum0i−1−(sum1j−sum1i−1)=sum0j−sum1j+sum1i−1−sum0i−1所以想让差值大,在右端点确定时要想办法快速确定左端点使得 s u m 1 i − 1 − s u m 0 i − 1 sum1_{i-1}-sum0_{i-1} sum1i−1−sum0i−1最大。而这个东西可以在来的过程中前缀最大值。
吐槽
所以这道题比上道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=p1⋅p2⋅...⋅pxd2=px+1⋅px+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;
}
一发入魂,爽!