“科大讯飞杯”第18届上海大学程序设计联赛春季赛暨高校网络友谊赛 补题

比赛链接:https://ac.nowcoder.com/acm/contest/5278/
H.纸牌游戏
题意:有n张牌,每张牌上有数字0-9,从中选m张牌,构成一个能被3整除的数,如果可以,输出最大值,否则输出-1。
解题思路:一个数能被3整除等价于数位之和能被3整除,既然要找最大的,最高位自然要选尽可能大的数字,当然你选的数量可能导致最后结果不满足条件,所以加选 max-1,max-2,至少有一种情况能满足条件(前提是本身有解),所以我们从高位dfs一下,然后取最大值即可。
还有一种方法,从高位选数,判断之后能否有解,主要注意check函数的写法。
代码如下:

#include 
using namespace std;
#define rep(i,a,b) for(register int i=a;i<=b;++i)
#define drep(i,a,b) for(register int i=a;i>=b;--i)
const int maxn=1e5+5;
char s[maxn],ans[maxn];
int cnt[10],c[3];
int T;
inline bool check(int n,int t)//判断这位选完后还能否成立
{
    rep(i,0,2)
    c[i]=0;
    rep(i,0,9)
    c[i%3]+=cnt[i];
    int down=max(0,n-c[1]-c[2]);//i下限
    int up=min(n,c[0]);//i上限
    drep(i,up,max(down,up-4))//枚举最大的3个i
    {
        int y=2*n-2*i-t;
        y=(y%3+3)%3;//需要达到的数
        int l=max(0,n-i-c[2]);//y的下限
        int r=min(c[1],n-i);//y的上限
        while(l%3!=y)
        l++;
        if(l<=r)
        return 1;
    }
    return 0;
}
int main()
{
    int n,m;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%s",s+1);
        n=strlen(s+1);
        scanf("%d",&m);
        rep(i,0,9)
        cnt[i]=0;
        rep(i,1,n)
        cnt[s[i]-'0']++;
        bool flag=1;//标记方案是否成立
        int c=0;//余数
        rep(i,1,m)//从高位向低位找
        {
            ans[i]=0;
            drep(j,9,0)//从 9 向 0 找
            if(cnt[j])
            {
                cnt[j]--;
                int t=3-c-j;
                t=(t%3+3)%3;
                bool tag=check(m-i,t);
                if(tag)
                {
                    ans[i]=j+'0';//更新答案
                    c=(c+j)%3;//更新余数
                    break;
                }
                cnt[j]++;//这位取j不满足要把之前减的加回去
            }
            if(!ans[i]||(i==1&&m>1&&ans[1]=='0'))//注意不能有前导零
            {
                flag=0;
                break;
            }
        }
        ans[m+1]=0;
        puts(!flag?"-1":ans+1);
    }
    return 0;
}

你可能感兴趣的:(算法,贪心)