【USACO2.2】解题报告

版权声明:若希望转载,在评论里直接说明即可,谢谢! https://blog.csdn.net/SSL_ZYC/article/details/84069379

前言

在这里插入图片描述
然而只有一道动态规划的题目。。。
其他三道题都是模拟。。。
难度还是有所增加的。至少在洛谷中又有一道蓝题了。
USACO:http://train.usaco.org


USACO2.2.3.Preface Numbering

思路:
我们会发现罗马数字中每一位都是独立的。不会遭到其他位的干扰。
例如数字 3197 3197 1132 1132 7108 7108 ,他们的百位都是 1 1 ,但是其他位没有一样的,但是百位的表示还都是不变的。
那么就一位一位地处理,从高位到低位一个一个输出即可。


代码:

#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

思路:
很明显的 D P DP 啊。
题目可以等价的转换为求在 1 n 1\sim n 中选出几个数使得和为 1 + 2 + 3 + . . . + n 2 \frac{1+2+3+ ... +n}{2} 的方案数。
很明显可以设 f [ i ] [ j ] f[i][j] 表示选完 i i 个数,和为 j j 的方案数。那么就有
f [ i ] [ j ] + = f [ i 1 ] [ j i ] f[i][j]+=f[i-1][j-i]
那么答案就是 f [ n ] [ 1 + 2 + 3 + . . . + n 2 ] = f [ n ] [ n ( 1 + n ) 2 2 ] = 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}]
那么如果 n ( 1 + n ) n(1+n) 不是 4 4 的倍数就输出 0 0 好了。


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


代码:

#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. 改变所有的数,循环结 1 1
  2. 两个数中改变一个,循环结 2 2
  3. 同上,循环结 2 2
  4. 三个数中改一个,循环结 3 3

L C M ( 1 , 2 , 2 , 3 ) = 6 LCM(1,2,2,3)=6
所以最终答案一定是每6个数为一个循环结
那么我们就只要维护这个数列的前 6 6 个数就可以了!
继续优化。
我们知道,任意一种改变方式按奇数次和偶数次是相同的,即你按 7 7 次, 101 101 次, 798132565 798132565 次, 2 x + 1 2x+1 次都是一样的,而你按 6 6 次, 198 198 次, 354357752 354357752 次, 2 x 2x 次都是一样的。
那么我们就对于每一种按钮枚举 0 1 0\sim1 ,表示按偶数下还是奇数下。那么如果满足:

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

那么就可以进行模拟,求出最终灯(的前六位),如果符合要求,就保存这个答案,最终排序输出即可。
时间复杂度: O ( 1 ) O(1) (常数 2 4 × 6 2^4\times 6 )。(或者说 O ( 2 4 × 6 ) O(2^4\times 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;
}

猜你喜欢

转载自blog.csdn.net/SSL_ZYC/article/details/84069379
今日推荐