第一题(noip):
题意:
给出一段长度为n的字符串,里面只有NOI三个字母组成,我们现在要求插入一个字符,这个字符可以等于NOI中的其中一个字符,求我们插入这个字符后我们可以有多少种选法选成NOI这一串。
思路:
我们可以求出原来没插入字符前选NOI这一串的选法,它就等于每个O的前面N的个数*每个O的后面的I的个数,例如,NOIOI中,它原来的选法等于N
OIOI:1*2,NOI
OI:1*1,我们把他们加起来就等于结果3,现在我们来一个一个枚举N和I的位置,N放进的最优值是每个O后I的数量的和,I放进的最优值是每个O前N的数量的和,之后还有O的位置,O的位置是max(res_O,a[i]*b[i]),a[i]表示i前面N的个数,b[i]表示i后面I的个数,之后我们求出放n,o,i的最优值的最大值再加上原来的选法就是答案。
代码:
#include<cstdio>
#include<cstring>
#include<string>
using namespace std;
int n,res_N,res_O,res_I,a[100001],b[100001],t,ti,o[100001],ks;
unsigned long long ans;
char c[100001];
unsigned long long max(int x,int y){return x>y?x:y;}
int main()
{
freopen("noip.in","r",stdin);
freopen("noip.out","w",stdout);
scanf("%d\n",&n);
gets(c);
for (int i=0;i<n;i++)
if (c[i]=='I') b[0]++;
for (int i=0;i<n;i++)
{
a[i]=a[i-1];
if(i)b[i]=b[i-1];
if (c[i]=='N') a[i]++;//求出i前位置N的个数
if (c[i]=='I') b[i]--;//求出i后位置I的个数
if (c[i]=='O'){ans+=a[i]*b[i];res_N+=b[i];res_I+=a[i];}//res_N和res_I分别代表放入N和I的最优值
} //ans是原来的选法
for (int i=0;i<n;i++)
res_O=max(res_O,a[i]*b[i]);求出放入O的最优值
printf("%lld",ans+max(res_N,max(res_O,res_I)));//加上插入字符后的值
}
第二题(小麦高度):
题意:
有两个人玩游戏,第一个人会选择n个小麦中最短的小麦把它延长到第二短的小麦,第二个人会选择最长的小麦把它剪短到第二长的小麦,先让第一个人开始游戏,当小麦的高度不足三种时,正在进行游戏的人就输了。
思路:
因为要求有几种高度,所以我们可以用桶来统计,设置两个东西来指向最高的桶和最低的桶,之后进行游戏,最后只剩两个桶的时候比较高桶的数量和低桶的数量,如果低桶数量小于高桶数量就是第二个人赢,否则就是第一个人赢,当然,如果游戏没开始前就只有两种高度我们就输出第二个人赢。
代码:
#include<cstdio>
int n,b[100001],x,high,low,m;
struct node{
int cnt,val;
}q[100001];
int min(int x,int y){return x<y?x:y;}
int main()
{
freopen("wheat.in","r",stdin);
freopen("wheat.out","w",stdout);
scanf("%d",&n);
for (int i=1;i<=n;i++)
{
scanf("%d",&x); b[x]++;//b表示桶
}
low=1;//low是最小的桶的下标
for (int i=1;i<=100000;i++)
if (b[i]) q[++high].cnt=b[i],q[high].val=i;//cnt存数量,val存数值
if (high-low+1<3) {printf("Sarah\n%d %d",q[low].val,q[high].val);return 0;}//判断一开始有几种高度
while (high-low+1>=3)
{
int kj=min(q[low].cnt,q[high].cnt);//快速进行游戏
q[low].cnt-=kj;q[high].cnt-=kj;
q[low+1].cnt+=kj;q[high-1].cnt+=kj;
if (q[low].cnt==0) low++;//如果最小的桶为0了我们就让下标加
if (q[high].cnt==0) high--;//如果最大的桶为0了我们就让下标减
}
if (q[low].cnt<=q[high].cnt) printf("Sarah\n%d %d",q[low].val,q[high].val);
else printf("Smart\n%d %d",q[low].val,q[high].val);//判断输出
}
第三题(铺设地板):
题意:
在一个矩阵里填字母,这些字母相同的连起来必须是正方形,1个字母也是个正方形,我们还要把这些字母从左到右,从上到下合起来,变成一个字符串,还要让这个字符串变得最小。
思路:
我们可以用贪心,可以从每个没有填过的地板来枚举那一块能填的最小字母,如果可以扩展成一个正方形,我们就扩展,但要注意我们能不能填那里。
代码:
#include<cstdio>
#include<algorithm>
using namespace std;
int n,m;
char c[101][101];
int ask(int x,int y)//这个是来判断当前能填的最小字母的
{
for (int i=0;i<=26;i++)
if (c[x][y+1]!='A'+i&&c[x-1][y]!='A'+i&&c[x][y-1]!='A'+i)//我们填的字母不能和相连的地板相同
return i;
}
bool ok(int x,int y,int l,int t)//判断此处能不能填边长为l最小字母为t的地板
{
return (!c[x][y+l]&&!c[x+l][y]&&t+'A'!=c[x+l][y-1]&&t+'A'!=c[x-1][y+l]&&t<=ask(x,y+l));//我们要填的话,就不
} //能和即将扩展的正方形的上左右边的字母相同
void fill(int x,int y)
{
int t=ask(x,y),l;
c[x][y]=t+'A';//t为最小字母
for (l=1;l<=min(n-x,m-y)&&ok(x,y,l,t);l++)//求出能扩展正方形的边长
c[x][y+l]=t+'A';
for (int i=x;i<=x+l-1;i++)
for (int j=y;j<=y+l-1;j++)
c[i][j]=t+'A';//扩展边长为l的正方形
}
int main()
{
freopen("floor.in","r",stdin);
freopen("floor.out","w",stdout);
scanf("%d%d",&n,&m);
for (int i=1;i<=n;i++)
for (int j=1;j<=m;j++)
if (!c[i][j]) fill(i,j);//如果没有填我们就去填
for (int i=1;i<=n;i++)
{
for (int j=1;j<=m;j++)
printf("%c",c[i][j]);
printf("\n");
}
}
第四题(邮票):
题意:
给出s种邮票,每封信最多贴e张邮票,我们要求能贴出多少种面值的邮票的最大连续集。
思路:
用f[i]代表贴出i最少需要的邮票张数,如果求出了f[j],则f[j+d[i]]=min(f[j]+1,f[j+d[i]]),其中d[i]表示s种邮票中其中一种的面值,初始化f[0]=0,之后就是统计最大连续集。
代码:
#include<cstdio>
#include<cstring>
int s,e,f[100001],d[21],m,tj,ans;
int min(int x,int y) {return x<y?x:y;}
int main()
{
freopen("stamp.in","r",stdin);
freopen("stamp.out","w",stdout);
memset(f,127/3,sizeof(f));
f[0]=0;
scanf("%d%d",&s,&e);
for (int i=1;i<=s;i++)
scanf("%d",&d[i]),m=d[i]>m?d[i]:m;
m*=e;
for (int i=1;i<=s;i++)
{
for (int j=0;j<=m;j++)
f[j+d[i]]=min(f[j]+1,f[j+d[i]]);//求出每种面值要贴的最少张数
}
for (int i=1;i<=m*e;i++)//因为最多只能贴出面值为m*e的邮票,所以只用
{
if (f[i]>e) f[i]=0;//如果要贴的张数大于e我们就让它等于0
if (f[i]!=0) tj++;//不等于0才统计最大连续集
else tj=0;ans=ans>tj?ans:tj;//如果等于0就让统计的tj清0,ans求最大值
}
printf("%d",ans);
}