前言

【USACO2.2】解题报告_罗马数字

然而只有一道动态规划的题目。。。

其他三道题都是模拟。。。

难度还是有所增加的。至少在洛谷中又有一道蓝题了。

USACO:​​http://train.usaco.org​


USACO2.2.3.​​Preface Numbering​

思路:

我们会发现罗马数字中每一位都是独立的。不会遭到其他位的干扰。

例如数字319731973197和113211321132和710871087108,他们的百位都是111,但是其他位没有一样的,但是百位的表示还都是不变的。

那么就一位一位地处理,从高位到低位一个一个输出即可。


代码:

#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​

思路:

很明显的DPDPDP啊。

题目可以等价的转换为求在1∼n1\sim n1∼n中选出几个数使得和为1+2+3+...+n2\frac{1+2+3+ ... +n}{2}21+2+3+...+n​的方案数。

很明显可以设f[i][j]f[i][j]f[i][j]表示选完iii个数,和为jjj的方案数。那么就有

f[i][j]+=f[i−1][j−i]f[i][j]+=f[i-1][j-i]f[i][j]+=f[i−1][j−i]

那么答案就是f[n][1+2+3+...+n2]=f[n][n(1+n)22]=f[n][n(1+n)4]f[n][\frac{1+2+3+ ... +n}{2}]=f[n][\frac{\frac{n(1+n)}{2}}{2}]=f[n][\frac{n(1+n)}{4}]f[n][21+2+3+...+n​]=f[n][22n(1+n)​​]=f[n][4n(1+n)​]

那么如果n(1+n)n(1+n)n(1+n)不是444的倍数就输出000好了。


优化:

可以利用背包的思想将第一位省略掉。


代码:

#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\n"); //特判
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\n",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("\n");
return 0;
}


USACO2.2.6.​​Party Lamps​

思路:

我们来看一下四种转化方法的循环结。

  1. 改变所有的数,循环结111。
  2. 两个数中改变一个,循环结222。
  3. 同上,循环结222。
  4. 三个数中改一个,循环结333。

LCM(1,2,2,3)=6LCM(1,2,2,3)=6LCM(1,2,2,3)=6

所以最终答案一定是​​每6个数为一个循环结​​。

那么我们就只要维护这个数列的前666个数就可以了!

继续优化。

我们知道,任意一种改变方式按奇数次和偶数次是相同的,即你按777次,101101101次,798132565798132565798132565次,2x+12x+12x+1次都是一样的,而你按666次,198198198次,354357752354357752354357752次,2x2x2x次都是一样的。

那么我们就对于每一种按钮枚举0∼10\sim10∼1,表示按偶数下还是奇数下。那么如果满足:

  1. 所有的按钮按下后的奇偶性和总按下次数相同。
  2. 枚举的循环变量相加不大于总按下次数。

那么就可以进行模拟,求出最终灯(的前六位),如果符合要求,就保存这个答案,最终排序输出即可。

时间复杂度:O(1)O(1)O(1)(常数24×62^4\times 624×6)。(或者说O(24×6)O(2^4\times 6)O(24×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("\n");
}
if (!sum) puts("IMPOSSIBLE");
return 0;
}