Codeforces&&DP总结
一.Codeforce:
4.16Codeforces Round #715 (Div. 2)
这次时第一次扣分得比赛!!!!太难受了
A. Average Height
这次A题非常之简单,只要把输入的数字的奇数和偶数分开然后输出就得到答案(奇数在前偶数在后或者偶数在前奇数在后),没有一点难度
AC代码:
#include <iostream>
using namespace std;
int a[2005],b[2005];
int main()
{
int t;
cin>>t;
while(t--)
{
int n;
cin>>n;
int i,j,k=1;
for(i=1;i<=n;i++)
cin>>a[i];
for(i=1;i<=n;i++)
{
if(a[i]%2==0)
{
b[k]=a[i];
k++;
}
}
for(i=1;i<=n;i++)
{
if(a[i]%2==1)
{
b[k]=a[i];
k++;
}
}
for(i=1;i<k-1;i++)
cout<<b[i]<<" ";
cout<<b[k-1]<<endl;
}
return 0;
}
这是比赛时写的代码,其实可以更简单,题目太简单了,就不再改进了
B. TMT Document
这个题其实没有想象中的难,但是当时思考的方向不对,一直在想着使用for循环然后找到M后再怎么操作,然后再用erase()删除该字符串的字符,只要两串字符就可以意识到这个思路行不通,(ex:TMTTMT,TMTMTT,如果遇到M后从两边查找T,第一个案例行不通,如果是从M到两边查找T,第二个案例行不通),但是并没有转换思路,因为这是第一次比赛的时候一开始思路就错的情况,没有想着改思路,到最后也没有做出来,现在一想,实在是太亏了,一条路走不到终点,就应该换一条路试试,如果道路是错误的,越往下走,做的都是无用功
正确思路:应该是使用for循环从一边开始进行,如果遇到M的数量大于T的时候,输出NO,再用for循环从另一边开始,同样遇到M的数量大于T的时候,输出NO,从另一边开始的原因的案例也很容易找到(ex:TTTMMTTTM,这时候如果只有从头尾的一次for循环,那么不会输出NO,就需要从另一边再使用一次for循环)
AC代码
#include <bits/stdc++.h>
using namespace std;
string a;
int main()
{
//freopen("in.txt","r",stdin);
int t;
cin>>t;
while(t--)
{
int flag=0,n,summ=0,sumt=0;
cin>>n>>a;
for(int i=0;i<a.size();i++)
{
if(a[i]=='T')
sumt++;
else summ++;
if(summ>sumt){flag=1;break;}
}
summ=sumt=0;
for(int i=a.size()-1;i>=0;i--)
{
if(a[i]=='T')
sumt++;
else summ++;
if(summ>sumt){flag=1;break;}
}
if(summ*2!=sumt) flag=1;
if(flag==1) cout<<"NO\n";
else cout<<"YES\n";
}
return 0;
}
其实也可以只需要一次for循环就能解决问题,只是输出NO的条件再加一条
if(sumt-summ>n/3)
cout<<"NO\n";
(ex:TTTMMTTTM,这个判断其实也挺容易理解,如果一边的T和M数量的差值大于总子数列的数目,可以理解为前面的一个T需要一个M与其配对,可是如果T-M的数量大于总序列数的时候,就说明TMT的后面的T与M不能完成配对)
4.12Educational Codeforces Round 107 (Rated for Div. 2)
这是打Codeforces以来出的最多题的一场比赛,而且还是Div2的比赛,不过Educational版本的,题目也非常简单
A. Review Site
我感觉A题就是锻炼读题的,读题准确,五分钟出代码,题目很长,意思很简单,就是求支持票和不知道投啥票的总和
AC代码:
#include <iostream>
using namespace std;
int main()
{
int t;
cin>>t;
while(t--)
{
int n,i,sum=0;
cin>>n;
int a[55];
for(i=1;i<=n;i++){
cin>>a[i];
if(a[i]==1||a[i]==3)
sum++;}
cout<<sum<<endl;
}
return 0;
}
B. GCD Length
这个题真是给我开了个玩笑,花了四五十分钟想出来的思路,结果只需要两行代码就能解决
这是比赛时AC的代码:
#include <iostream>
using namespace std;
int gcd(int a,int b)
{
return b==0?a:gcd(b,a%b);
}
int main()
{
int t;
cin>>t;
while(t--)
{
int a,b,c;
cin>>a>>b>>c;
int m=1,n=1,minn=1,maxx=1;
for(int j=1;j<a;j++)
n=n*10;
for(int j=1;j<b;j++)
m=m*10;
for(int j=1;j<=c;j++)
{
maxx=maxx*10;minn=maxx;
}
maxx--;
minn/=10;
int temp=gcd(n,m);
for(;;temp=gcd(n,m)){
if(temp<=maxx&&temp>=minn)//这里要使用temp变量,在之前赋值为gcd(n,m),如果直接使用gcd(n,m)会超时
{
cout<<n<<" "<<m<<endl;
break;
}
m++;
}
}
return 0;
}
看了别人的AC代码,就像对我说,这题也需要敲代码?
别人AC代码:
#include<bits/stdc++.h>
using namespace std;
int a,b,c,t;
int main(){
cin>>t;
while(t--){
cin>>a>>b>>c;
int x=pow(10,a-1)+pow(10,c-1);
int y=pow(10,b-1);
cout<<x<<" "<<y<<endl;
}
}
没错,这就需要理解好题意了,其实一开始我把第一个数赋值为10是非常不确定的,因为不知道10对应的另外一个数是多少,如果很大的话,很可能超时,但是当看完别人的代码,我发现,是我多虑了,其实CF的比赛Div3和Div2的前几道题都是可以用非常简单的代码进行实现的,思路也挺简单,就看能不能想起来了
C. Yet Another Card Deck
一开始写代码的时候是非常不敢写的,因为给的数值太大了,思路中需要使用二重循环才能够解决问题,所以到最后也是本着搏一搏的心态去提交的,思路很简单,找到一个数,把该数提到最前面,把该数前面的数往后挪一个位置,输出该数原始位置
AC代码
#include <iostream>
using namespace std;
int t[300005],a[300005];
int main()
{
int n,q,temp;
int i,j;
cin>>n>>q;
for(i=1;i<=n;i++)
cin>>a[i];
for(i=1;i<=q;i++)
cin>>t[i];
for(i=1,j=1;j<=q;i++)
{
if(a[i]==t[j])
{
if(j!=q)
{
cout<<i<<" ";
temp=a[i];
for(int k=i;k>1;k--)
a[k]=a[k-1];
a[1]=temp;
j++;
i=0;
continue;
}
if(j==q)
{cout<<i;break;}
}
}
return 0;
}
4.11Divide by Zero 2021 and Codeforces Round #714 (Div. 2)
A. Array and Peaks
思路:根据Peaks的数量,从大到小,按照2,4,6,8…的位置顺序进行插入,剩下的依次排序
AC代码:
#include <iostream>
using namespace std;
int main()
{
int t;
cin>>t;
while(t--)
{
int a[105]={0};
int n,k,i,j,num=0;
cin>>n>>k;
a[1]=1;
if(k==0)
{
for(i=2;i<=n;i++)
a[i]=i;
for(i=1;i<=n;i++)
{
if(i!=n)
cout<<a[i]<<' ';
else cout<<a[i]<<endl;
}
}
else
{
for(i=2,j=0;i<=n;i+=2,j++)
{
if(i!=n)
{
a[i]=n-j;
num++;
}
if(num==k)
break;
}
if(num==k)
{
for(i=2,j=2;i<=n;i++)
{
if(a[i]==0)
{
a[i]=j;
j++;
}
}
for(i=1;i<=n;i++)
{
if(i!=n)
cout<<a[i]<<' ';
else cout<<a[i]<<endl;
}
}
else if(num<k) cout<<"-1"<<endl;
}
}
return 0;
}
二.DP总结
题目网址:https://vjudge.net/contest/430905
G.
这个题提交了17遍,在AC后又提交了12遍,只为了研究明白一个问题,定义全局变量数组和局部变量数组的问题,这个问题很容易导致Runtime Error(ACCESS_VIOLATION)
对于全局变量和局部变量,这两种变量存储的位置不一样。对于全局变量,是存储在内存中的静态区(static),而局部变量,则是存储在栈区(stack)。
这里,顺便普及一下程序的内存分配知识:
C语言程序占用的内存分为几个部分:
1、堆区(heap):由程序员分配和释放,比如malloc函数
2、栈区(stack):由编译器自动分配和释放,一般用来存放局部变量、函数参数
3、静态区(static):用于存储全局变量和静态变量
4、代码区:用来存放函数体的二进制代码
在C语言中,一个静态数组能开多大,决定于剩余内存的空间,在语法上没有规定。所以,能开多大的数组,就决定于它所在区的大小了。
在WINDOWS下,栈区的大小为2M,也就是210241024=2097152字节,一个int占2个或4个字节,那么可想而知,在栈区中开一个int[1000000]的数组是肯定会overflow的。我尝试在栈区开一个2000000/4=500000的int数组,仍然显示overflow,说明栈区的可用空间还是相对小。所以在栈区(程序的局部变量),最好不要声明超过int[200000]的内存的变量。
而在静态区(我没有在网上找到它具体大小的数据,但是可以肯定比栈区大),用vs2010编译器试验,可以开2^32字节这么大的空间,所以开int[1000000]没有问题。
总而言之,当需要声明一个超过十万级的变量时,最好放在main函数外面,作为全局变量。否则,很有可能overflow。
参考:https://blog.csdn.net/cjsycyl/article/details/9230429?utm_source=app&app_version=4.5.8
写了这么多,现在说一下思路吧
思路:数组小,可以先不考虑超时问题,二重循环用来给移动dp,二重循环里面套二重循环,在能量的基础上给每一个可以到达的位置进行增值,暴力,其实在数组只有100的时候就可以想到这个办法
AC代码:
#include <iostream>
#include <string>
using namespace std;
int main()
{
int t;
cin>>t;
while(t--)
{
int i,j,n,m;
int a[105][105];
int dp[1115][1115];
memset(dp,0,sizeof(dp));
cin>>n>>m;
for(i=1;i<=n;i++)
for(j=1;j<=m;j++)
cin>>a[i][j];
dp[1][1]=1;
for(i=1;i<=n;i++)
for(j=1;j<=m;j++)
{
if(dp[i][j]){
int d=a[i][j];
int x,y;
for(x=0;x<=d;x++)
{
for(y=0;y<=d;y++)
{
if(x+y==0)
continue;
if(x+y>d)
break;
int di=x+i,dj=y+j;
if(x+i<=n&&y+j<=m)
dp[di][dj]+=dp[i][j];
dp[di][dj]%=10000;
}
}
}
}
cout<<dp[n][m]%10000<<endl;
}
}
Y.
思路:dp[i][t],t表示字符串末尾的数字大小,i控制字符串长度,dp[i][j],j表示一组数中的最大值,用for循环进行扫描,j等于一个数的时候t每次增加一个j,运行关系式
dp[i+1][t]=(dp[i+1][t]+dp[i][j])%mod;
其实就是让结尾的数每次增加一个j,确保前面的数除以后面的数没有余数
AC代码:
#include <iostream>
#define mod 1000000007//第一次使用预处理宏感觉还不错嘿嘿嘿
using namespace std;
int dp[2005][2005];
int main() {
int n,k,sum=0;
cin>>n>>k;
dp[0][1]=1;
for(int i=0;i<=k;i++)
for(int j=1;j<=n;j++)
for(int t=j;t<=n;t+=j)
dp[i+1][t]=(dp[i+1][t]+dp[i][j])%mod;
for(int i=1;i<=n;i++)
sum=(sum+dp[k][i])%mod;
cout<<sum;
}
三.感悟
这个星期的重心偏向于Codeforces的比赛,4.10,4.11,4.12连续三天打的虽然累,但是收获也不小,参悟透了一点CF的题目,这几天打比赛,让我突然想到了一句话,当你排除所有的不可能,最后一个结果就是可能,我称之为,“穷举法”,虽然很笨,但是在做题的时候可以尝试一下,嘿嘿嘿…
线性DP的题目也告一段落了,最近学习了局部DP,感觉差别不大,但是线性DP的题都没很懂,一下又换到局部DP,危机感涌向心头…ACM也逐渐融入生活,慢慢成了生活里的一部分,选ACM的室友在努力的同时,也会促使自己努力,跟着舍友一起熬夜打比赛,一起竞争或者一起合作,合作中竞争,竞争中合作,或许,这就是ACM的快乐吧