前言
然而只有一道动态规划的题目。。。
其他三道题都是模拟。。。
难度还是有所增加的。至少在洛谷中又有一道蓝题了。
USACO:http://train.usaco.org
USACO2.2.3.Preface Numbering
思路:
我们会发现罗马数字中每一位都是独立的。不会遭到其他位的干扰。
例如数字和和,他们的百位都是,但是其他位没有一样的,但是百位的表示还都是不变的。
那么就一位一位地处理,从高位到低位一个一个输出即可。
代码:
#include <cstdio>
#include <iostream>
using namespace std;
const char ch[8]={' ','I','V','X','L','C','D','M'};
const int num[8]={0,1,5,10,50,100,500,1000};
int n,ans[8];
void work(int x,int i)
{
if (x==1) ans[i*2-1]++; //按位输出
if (x==2) ans[i*2-1]+=2;
if (x==3) ans[i*2-1]+=3;
if (x==4) ans[i*2]++,ans[i*2-1]++;
if (x==5) ans[i*2]++;
if (x==6) ans[i*2]++,ans[i*2-1]++;
if (x==7) ans[i*2]++,ans[i*2-1]+=2;
if (x==8) ans[i*2]++,ans[i*2-1]+=3;
if (x==9) ans[i*2+1]++,ans[i*2-1]++;
}
int main()
{
scanf("%d",&n);
for (int j=1;j<=n;j++)
{
int x=j;
for (int i=1;x;i++)
{
work(x%10,i); //一位一位输出
x/=10;
}
}
for (int i=1;i<=8;i++)
if (ans[i])
cout<<ch[i]<<' '<<ans[i]<<endl;
return 0;
}
USACO2.2.4.Subset Sums
思路:
很明显的啊。
题目可以等价的转换为求在中选出几个数使得和为的方案数。
很明显可以设表示选完个数,和为的方案数。那么就有
那么答案就是
那么如果不是的倍数就输出好了。
优化:
可以利用背包的思想将第一位省略掉。
代码:
#include <cstdio>
#define ll long long
using namespace std;
int n,m;
ll f[1300];
int main()
{
scanf("%d",&n);
m=(n+1)*n/2;
if (m%2) return !printf("0
"); //特判
m/=2;
f[0]=1;
for (int i=1;i<=n;i++)
for (int j=m;j>=i;j--) //省略一维之后一定要倒序!
f[j]+=f[j-i];
printf("%lld
",f[m]/2);
return 0;
}
USACO2.2.5.Runaround Numbers
思路:
直接暴力模拟,每一个数判断一下即可。
毫无难度。注意细节。
代码:
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
int m,n,len,a[30];
bool vis[30],ok;
bool check()
{
memset(vis,0,sizeof(vis));
int x=1;
for (int i=1;i<=len;i++)
{
x=(x+a[x])%len;
if (!x) x=len;
if (vis[x]) return 0; //不是回文数
vis[x]=1;
}
return 1;
}
int main()
{
scanf("%d",&m);
do
{
Continue:
m++;
n=m;
len=0;
ok=1;
memset(vis,0,sizeof(vis));
while (n)
{
a[++len]=n%10;
if (vis[a[len]]) goto Continue; //break诡异的出错了,所以选择了goto。但是建议少用goto!
vis[a[len]]=1;
n/=10;
}
for (int i=1;i<=len/2;i++)
swap(a[i],a[len-i+1]);
}
while (!check());
for (int i=1;i<=len;i++)
printf("%d",a[i]);
printf("
");
return 0;
}
USACO2.2.6.Party Lamps
思路:
我们来看一下四种转化方法的循环结。
- 改变所有的数,循环结。
- 两个数中改变一个,循环结。
- 同上,循环结。
- 三个数中改一个,循环结。
所以最终答案一定是每6个数为一个循环结
。
那么我们就只要维护这个数列的前个数就可以了!
继续优化。
我们知道,任意一种改变方式按奇数次和偶数次是相同的,即你按次,次,次,次都是一样的,而你按次,次,次,次都是一样的。
那么我们就对于每一种按钮枚举,表示按偶数下还是奇数下。那么如果满足:
- 所有的按钮按下后的奇偶性和总按下次数相同。
- 枚举的循环变量相加不大于总按下次数。
那么就可以进行模拟,求出最终灯(的前六位),如果符合要求,就保存这个答案,最终排序输出即可。
时间复杂度:(常数)。(或者说)
代码:
#include <cstdio>
#include <algorithm>
using namespace std;
int n,m,x,sum;
bool o[7],c[7];
struct answer
{
int num[7];
}ans[30];
bool cmp(answer x,answer y) //从小到大排序
{
for (int i=1;i<=6;i++)
if (x.num[i]<y.num[i]) return 1;
else if (x.num[i]>y.num[i]) return 0;
return 0;
}
bool check(int i,int j,int k,int l)
{
//i^j^k^l表示最终的奇偶性,因为:
//0表示偶数,1表示奇数
//1^1=0,奇数+奇数=偶数
//0^0=0,偶数+偶数=偶数
//1^0=0^1=1,奇数+偶数=偶数+奇数=奇数
return ((i^j^k^l)==(m&1))&&(i+j+k+l<=m);
}
void work(int i,int j,int k,int l)
{
int a[7]={1,1,1,1,1,1,1};
if (i)
for (int q=1;q<=6;q++) a[q]^=1;
if (j)
for (int q=1;q<=6;q+=2) a[q]^=1;
if (k)
for (int q=2;q<=6;q+=2) a[q]^=1;
if (l)
for (int q=1;q<=6;q+=3) a[q]^=1;
for (int q=1;q<=6;q++)
if ((o[q]&&(!a[q]))||(c[q]&&a[q])) return; //判断是否符合要求
sum++;
for (int q=1;q<=6;q++)
ans[sum].num[q]=a[q]; //记录答案
}
int main()
{
scanf("%d%d",&n,&m);
while (1)
{
scanf("%d",&x);
if (x==-1) break;
o[(x-1)%6+1]=1; //open,开着的灯
}
while (1)
{
scanf("%d",&x);
if (x==-1) break;
c[(x-1)%6+1]=1; //close,关着的灯
}
for (int i=0;i<=1;i++)
for (int j=0;j<=1;j++)
for (int k=0;k<=1;k++)
for (int l=0;l<=1;l++)
if (check(i,j,k,l)) work(i,j,k,l);
sort(ans+1,ans+1+sum,cmp);
for (int i=1;i<=sum;i++)
{
for (int j=1;j<=n;j++)
putchar(ans[i].num[(j-1)%6+1]+48);
printf("
");
}
if (!sum) puts("IMPOSSIBLE");
return 0;
}