[CQOI2009] 循环赛 - 暴力出奇迹


题目描述

  n支队伍打比赛,每两支队伍恰好比赛一场。平局时各得1分,而有胜负时胜者3分,负者0分。
  假设三支队伍得分分别为3, 3, 3,则可能有两种情况:
  这里写图片描述
  给出n支队伍的最终得分(即所有比赛均已结束),统计有多少种可能的分数表。


输入格式

第一行包含一个正整数n,队伍的个数。第二行包含n个非负整数,即每支队伍的得分。


输出格式

输出仅一行,即可能的分数表数目。保证至少存在一个可能的分数表。


样例数据

样例输入

样例数据#1
3
3 3 3
样例数据#2
2
0 3
样例数据#3
3
4 1 2
样例数据#4
6
5 6 7 7 8 8

样例输出

样例数据#1
2
样例数据#2
1
样例数据#3
1
样例数据#4
121


数据规模

这里写图片描述


题目分析

麻烦的搜索题,加了无数个剪枝和优化终于过了。。。
不过比起斗地主、Mayan游戏、素数方阵这些厉害的搜索题来说还算轻松
数据很良心,纯粹爆搜可以得88分左右
剪枝1:
可行性剪枝,如果当前队伍剩下的全赢也不能达到目标或当前队伍已经超过了目标,剪。
优化2:
由于棋盘是类对称的,故只需要枚举一半的棋盘
优化3:
因为平局总分数+2,非平局总分数+3,故可以列方程求出平局与非平局的数量,作为资源分配到搜索中
剪枝4:
不必搜索出情况后检查可行性,直接计算出每一个队伍最后一场比赛的得分
优化5:
大幅度提高时间效率:Hash判重。将剩余状态的方案用Hash保存,下一次直接调用。(有点碰运气的成分)


源代码

#include<algorithm>
#include<iostream>
#include<iomanip>
#include<cstring>
#include<cstdlib>
#include<vector>
#include<cstdio>
#include<cmath>
#include<queue>
using namespace std;
inline const int Get_Int() {
    int num=0,bj=1;
    char x=getchar();
    while(x<'0'||x>'9') {
        if(x=='-')bj=-1;
        x=getchar();
    }
    while(x>='0'&&x<='9') {
        num=num*10+x-'0';
        x=getchar();
    }
    return num*bj;
}
typedef long long LL;
int n,Remain[15],vst[9][174109],Hash[9][174109];
LL Dfs(int x,int y,LL Win,LL Equal) {
    if(y==1) { //Hash判重
        int tmp[15]= {0},num=0;
        for(int i=1; i<=n-(x-1); i++)tmp[i]=Remain[(x-1)+i];
        sort(tmp+1,tmp+n-(x-1)+1);
        for(int i=1; i<=n-(x-1); i++)num=(num*23+tmp[i])%174107;
        if(!vst[n-(x-1)][num]) {
            Hash[n-(x-1)][num]=Dfs(x,x+1,Win,Equal);
            vst[n-(x-1)][num]=1;
        }
        return Hash[n-(x-1)][num];
    }
    if(x>=y)return Dfs(x,x+1,Win,Equal); //只枚举一半棋盘
    if(x==n&&y==n+1)return 1; //找到一个可能性
    if((n-y+1)*3<Remain[x]||Remain[x]<0)return 0; //可行性剪枝
    int Nextx=x,Nexty=y;
    LL sum=0;
    if(Nexty==n) { //最后一个位置 :计算得出
        if(Remain[x]==1&&Equal>=1) {
            Remain[y]--;
            sum+=Dfs(x+1,1,Win,Equal-1);
            Remain[y]++;
        } else if(Remain[x]==0&&Win>=1) {
            Remain[y]-=3;
            sum+=Dfs(x+1,1,Win-1,Equal);
            Remain[y]+=3;
        } else if(Remain[x]==3&&Win>=1)sum+=Dfs(x+1,1,Win-1,Equal);
        return sum;
    } else Nexty++;
    if(Equal>=1&&Remain[x]>=1&&Remain[y]>=1) {
        Remain[x]--;
        Remain[y]--;
        sum+=Dfs(Nextx,Nexty,Win,Equal-1);
        Remain[x]++;
        Remain[y]++;
    }
    if(Win>=1) {
        if(Remain[x]>=3) {
            Remain[x]-=3;
            sum+=Dfs(Nextx,Nexty,Win-1,Equal);
            Remain[x]+=3;
        }
        if(Remain[y]>=3) {
            Remain[y]-=3;
            sum+=Dfs(Nextx,Nexty,Win-1,Equal);
            Remain[y]+=3;
        }
    }
    return sum;
}
LL sum=0,Win,Equal;
int main() {
    n=Get_Int();
    for(int i=1; i<=n; i++) {
        Remain[i]=Get_Int();
        sum+=Remain[i];
    }
    Win=abs(sum-(n*(n-1))); //赢或输的总次数
    Equal=abs((n*(n-1)/2)-Win); //平局总次数
    printf("%lld\n",Dfs(1,2,Win,Equal));
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值