贪心(打表) 乱搞(暴力) 博弈(猜结论) 专场

说在前面

反正这些东西练了还不是不会。。。

还有为什么我要抄课件题解啊喂

CodeForces 39A     C*++ Calculations

题目大意

  给一个单变量、常系数的C++式子
  定义模糊的部分按任意次序计算
  比如a+++++a到底先a++还是++a
  给定这个变量的初值,求最大可能值
  例子:
  5*a++-3*++a+a++
  a+++++a

解析

  贪心,按照变量前面的系数大小来贪心,系数越小越先算。
  证明如下

  一方面,a+++++a以任意顺序算出来的结果是一样的
  另外一方面,考虑4个式子,k*++a+m*a++,k*++a+m*++a,k*a+++m*a++,k*a+++m*++a
  以k*++a+m*a++为例
  先算第一个再算第二个,k*(a+1)+m*(a+1)
  反过来,k*(a+2)+m*a,即系数越小的越先算

  证明中并没有要求a的值的正负,所以就算是负数证明也是成立的
  其实感觉这个题还好,主要是实现起来有一些麻烦。

  因为没有常数项,所以考虑按符号,系数,字母这样的顺序进行处理,与洛谷P1022的处理类似

  代码如下(丑陋如斯)

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int a,ans,cnt,len;char ch[1000005];
struct node{int k,id;}q[1000005];
bool num(int a){return '0'<=a&&a<='9';}
bool cmp(node a,node b){return a.k==b.k?a.id<b.id:a.k<b.k;}
int main()
{
    scanf("%d%s",&a,ch+1);len=strlen(ch+1);
    for(int i=1;i<=len;i++)
    {
        if(ch[i]=='a')
        {
            if(i==1||ch[i-1]=='+')q[++cnt]=(node){1,1};
            if(ch[i-1]=='-')q[++cnt]=(node){-1,1};i+=3;continue;
        }
        if(ch[i]=='+')
        {
            if(i==1||ch[i-1]=='+')q[++cnt]=(node){1,0};
            if(ch[i-1]=='-')q[++cnt]=(node){-1,0};i+=3;continue;
        }
        if(num(ch[i]))
        {
            int n=0,be=i;
            while(num(ch[i]))n=n*10+ch[i]-'0',i++;i++;
            if(ch[i]=='a')
            {
                if(be==1||ch[be-1]=='+')q[++cnt]=(node){n,1};
                if(ch[be-1]=='-')q[++cnt]=(node){-n,1};i+=3;continue;
            }
            if(ch[i]=='+')
            {
                if(be==1||ch[be-1]=='+')q[++cnt]=(node){n,0};
                if(ch[be-1]=='-')q[++cnt]=(node){-n,0};i+=3;continue;
            }
        }
    }
    sort(q+1,q+1+cnt,cmp);
    for(int i=1;i<=cnt;i++)
    {
        if(q[i].id==1)
            ans+=q[i].k*(a++);
        else
            ans+=q[i].k*(++a);
    }
    printf("%d\n",ans);
}

CodeForces 45G     Prime Problem

题目大意

  把1~n这n个数分成若干组,使得每组内的数的和是质数。任何一种分法都可以要求组数最少
  n ≤ 6000

解析

  构造
  用哥德巴赫猜想(小范围的正确性?),一个大于2的偶数可以被拆分成两个质数的和

 
  1~n的和为质数,只用分一组
  1~n的和为偶数,拆成两个质数
  1~n的和为奇数,拆成3+两个质数或者2+质数
  显然是组数最少的分法

扫描二维码关注公众号,回复: 7141255 查看本文章

  然而一开始我忘了哥德巴赫猜想是个什么

  代码

 

#include<cstdio>
int n,sum,col[6005];
bool pan(int a){for(int i=2;i*i<=a;i++)if(a%i==0)return 0;return 1;}
int main()
{
    scanf("%d",&n);sum=n*(n+1)/2;
    if(pan(sum)){for(int i=1;i<=n;i++)printf("1%c",i==n?'\n':' ');return 0;}
    if(sum%2)
    {
        if(pan(2)&&pan(sum-2))
        {for(int i=1;i<=n;i++)printf("%d%c",i==2?2:1,i==n?'\n':' ');return 0;}
        int ans;
        for(int i=2;i<=n;i++)
        {
            if(i==3)continue;
            if(pan(i)&&pan(sum-3-i)){ans=i;break;}
        }
        col[3]=2;col[ans]=3;
        for(int i=1;i<=n;i++)printf("%d%c",col[i]?col[i]:1,i==n?'\n':' ');
    }
    else
    {
        int ans;
        for(int i=2;i<=n;i++)if(pan(i)&&pan(sum-i)){ans=i;break;}
        for(int i=1;i<=n;i++)printf("%d%c",i==ans?2:1,i==n?'\n':' ');
    }
    return 0;
}

OpenJ_POJ C16D     Extracurricular Sports

题目大意

  找n个不同的数a1,…,an
  使得lcm(a1,…,an)=a1+…+an
  只需要求一个任意可行解
  n ≤ 200,ai ≤ 10^100

解析

  发现等比数列1,p,p^2,p^3......的前n-1项和为(p^n-1)/(p-1),lcmp^(n-1)    ←说得好像高中还学了什么其他数列似的

  除数是p-1就很烦,所以考虑p2

    1,2,4,8,16,32......

   就有和为2^n-1,lcm2^(n-1)
   和那里少了一个1,就把2变成3

  1,3,4,8,16,32......

  和就变为2^n = 2*2^(n-1)lcm3*2^(n-1),少了一个2^(n-1)但可以添加一个3的因子

    再把2^(n-2)这一项乘上一个3,就可以弥补那个2^(n-1)

  这是前n-1的情况,推一下n的情况可以发现答案为

   3*2^(n-1),2^n,2^(n-2),…,4,3,1

  还有,这个题要写高精度。。。

  代码

#include<cstdio>
#include<cstring>
int T,n,num[105],sp[105];
void mul(){for(int i=1;i<=100;i++)num[i]*=2;for(int i=1;i<=100;i++)if(num[i]>9)num[i+1]+=num[i]/10,num[i]%=10;}
void mul3(){for(int i=1;i<=100;i++)sp[i]=num[i]*3;for(int i=1;i<=100;i++)if(sp[i]>9)sp[i+1]+=sp[i]/10,sp[i]%=10;}
void print(){for(int i=100,flag=0;i>=1;i--){flag|=(num[i]>0);if(flag)printf("%d",num[i]);}puts("");}
void print1(){for(int i=100,flag=0;i>=1;i--){flag|=(sp[i]>0);if(flag)printf("%d",sp[i]);}puts("");}
int main()
{
    scanf("%d",&T);
    while(T--)
    {
        memset(num,0,sizeof num);memset(sp,0,sizeof sp);num[1]=1;
        scanf("%d",&n);
        if(n==2){printf("-1\n");continue;}if(n==3){printf("10\n20\n30\n");continue;}
        for(int i=1;i<n;i++)
        {
            if(i==2){printf("3\n");mul();continue;}
            if(i==n-1){mul3();mul();print();print1();continue;}
            print();mul();
        }
        puts("");
    }
}

POJ 3372     Candy Distribution  

题目大意

  n个小孩围成一个圈,老师顺时针隔0,1,2,…个小孩发糖,问每个小孩是否都能领到糖。
  n ≤ 10^9

复制粘贴课件的解析

  通过找规律可以发现,若n2的幂,那么就可以,反之不行
  证明
  设f(x)=x(x-1)/2,那么相当于证明f(1),…,f(n)构成n的完全剩余系
  即证明对于所有的i,j,f(i)!=f(j) (mod n)
  i*(i-1)/2 != j*(j-1)/2 (mod n)
  (i-j)*(i+j+1)/2 != 0 (mod n)
  i-ji+j+1的奇偶性不同,故(i-j)*(i+j+1)/2必为奇数*一个数的形式
  由i,j的任意性,只有当n2的幂的时候才成立
  应该是这些题中代码量最短的代码

#include<cstdio>
long long n,a[35];
int main()
{
    a[0]=1;
    for(int i=1;i<=30;i++)a[i]=a[i-1]*2ll;
    while(scanf("%lld",&n)!=EOF)
    {
        int flag=0;
        for(int i=0;i<=30;i++)
            flag|=(a[i]==n);
        if(flag)puts("YES");
        else puts("NO");
    }
    return 0;
}

HDU 4642     Fliping game

题目大意

  一个n*m棋盘上每个格子摆放一个硬币,有正有反
  每次选择一枚正面的硬币(x,y),从(x,y)向右下角所有的硬币都被翻转
  当某个人无法翻硬币(所有硬币都是反面) ,这个人判输
  1 ≤ n,m ≤ 100

解析

  每次最右下角的硬币都必被翻(谁一来就想得到啊喂)
  所以若一开始右下角的硬币是正面,那么后手的人一定把这个硬币翻回正面,从而让先手的人有硬币可翻
  从而这枚硬币决定了胜负。
  所以,这就是细节决定成败(雾

  代码

 

#include<cstdio>
long long T,n,m,cnt,a[105][105];
int main()
{
    scanf("%lld",&T);
    while(T--)
    {
        scanf("%lld%lld",&n,&m);
        for(int i=1;i<=n;i++)
            for(int j=1;j<=m;j++)
                scanf("%lld",&a[i][j]);
        if(a[n][m]==1)puts("Alice");
        else puts("Bob");
    }
    return 0;
}

HDU 2516     取石子游戏

题目大意

  1堆石子有n个,两人轮流取
  先取者第1次可以取任意多个,但不能全部取完
  以后每次取的石子数不能超过上次取子数的2倍
  取完者胜
  2 ≤ n < 2^31

一言不合抄课件解析

  找规律可知若n是斐波那契数,那么先手必败,否则必胜
  证明
  斐波那契数的情形,首先f[1]=2的情形先手必败,用归纳法
  f[n]=f[n-1]+f[n-2]
  先手不能取超过f[n-2]的石子,因为f[n-1]<2*f[n-2]
  那么由归纳假设可知一定是后手取到f[n-2]这堆石子的最后一颗
  但后手取完f[n-2]这堆石子的最后一颗后,先手并不能一下子取完f[n-1]这堆石子,由归纳假设可得也是后手取到f[n-1]这堆石子的最后一颗
  非斐波那契数的情形,由Zeckendorf定理,任何正整数可以表示为若干个不连续的斐波那契数之和
  当n不是斐波那契数时,n=f[a1]+f[a2]+…+f[ap] (p>1)
  a1>a2>…>ap
  由于不连续,所以先手的人可以一下子取完ap这堆石子
  且后手的人不能一下子取完a(p-1)这堆石子
  那么对于后手的人而言,是先手取a(p-1)这堆石子,结果是先手的人取完a(p-1)这堆石子
  所以先手必胜
  代码

#include<cstdio>
long long n,fib[55];
int main()
{
    fib[1]=fib[2]=1;for(int i=3;i<=50;i++)fib[i]=fib[i-1]+fib[i-2];
    while(scanf("%lld",&n)&&n)
    {
        int flag=0;
        for(int i=1;i<=50;i++)flag|=(n==fib[i]);
        if(flag)puts("Second win");
        else puts("First win");
    }
    return 0;
}

 

HDU 3032    Nim or not Nim?

题目大意

  n堆石子,每一次可以从任意一堆中拿走任意个石子,也可以将一堆石子分为两个小堆
  先拿完者获胜
  n ≤ 10^6, 石子数量在int范围内

解析

  sg函数和sg定理
  sg(0)=0,sg(1)=mex(sg(0))=1,sg(2)=mex(sg(0),sg(1),sg(1)^sg(1))=2
  递推对于此题的大范围是不可行的
  通过sg函数找规律,来做大范围的情况

  代码

#include<cstdio>
int T,i,n,x,ans;
int main()
{
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d",&n);
        for(ans=0,i=1;i<=n;i++)
        {
            scanf("%d",&x);
            if(x%4==3)ans^=x+1;
            if(x%4==0)ans^=x-1;
            if(x%4==1||x%4==2)ans^=x;
        }
        if(ans)puts("Alice");
        else puts("Bob");
    }
    return 0;
}

HDU 5754    Life Winner Bo

题目大意

  有一枚棋子,从(1,1)到(n,m),两人轮流走,先到的赢
  棋子是以下四种类型之一:
  王:只能右、下和斜着走一格。
  皇后:和王一样,但是可以任意步。
  骑士:只能右、下,但是可以任意步。
  马:走日(右下方)。
  2 ≤ n,m ≤ 1000

解析

  王的情形
  用sg函数打表找规律
  sg(1,1)=0
  sg(x,y)=mex(sg(x-1,y),sg(x,y-1),sg(x-1,y-1))
  后的情形
  相当于两堆物品分别有n,m个,每次从任一堆取至少一个或同时从两堆中取同样多个,最后取光者胜
  wythoff博弈
  骑士的情形
  相当于两堆物品分别有n,m个,每次从任一堆取至少一个,最后取光者胜
  nim博弈
  马的情形
  题目要求不能移动且没有到右下角时为平手
  对于当前位置,如果能走到必败态,那么一定是必胜态
  若不能走到必败态,那么如果能走到平局位置,则一定是平局

  然后。。。。打表大法好

  代码

#include<cmath>
#include<cstdio>
#include<algorithm>
using namespace std;
int T,type,n,m;
int main()
{
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d%d%d",&type,&n,&m);
        if(type==1){if(n%2==1&&m%2==1)puts("G");else puts("B");}
        if(type==2){if(n^m)puts("B");else puts("G");}
        if(type==3)
        {
            if((n+m)%3!=2)puts("D");
            else if(m==n)puts("G");
            else if(abs(n-m)==1)puts("B");
            else puts("D");
        }
        if(type==4)
        {
            n--,m--;
            if(n>m)swap(n,m);int k=m-n;
            if(int(double(k)*((1.0+sqrt(5.0))/2.0))==n&&int(double(k)*((3.0+sqrt(5.0))/2.0))==m)
            puts("G");else puts("B");
        }
    }
    return 0;
}

 

猜你喜欢

转载自www.cnblogs.com/firecrazy/p/11435146.html