[UR #1]UOJ 22 外星人 - dp

发现成功取模的模数从大到小严格递减并且次数不超过log,而且把数字从大到小排序之后,每钦定当前成功取模,大于当前取模之前的余数的数字可以随意排列,剩下的所有数字(当前模数除外)都要排在当前模数之后,因此方法是先随意排那些大于当前取模之前的余数的数字,然后序列中找mex(注意不是max),放钦定的模数,然后放当前和上一次取模之间小于取模之前余数的数字即可保证序列合法。最后我的做法是dp[i][j]表示最后一次成功取模位置是i,余数j,那么求第一问就找一个< a[n]的j即可。转移预处理一个nxt[j]表示序列从大到小排序后第一个小于等于j的位置,然后从这里开始转移即可,感觉复杂度是对的。
刚刚发现好像有代码非常非常短的做法,不明觉厉。

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#define N 1010
#define M 5010
#define gc getchar()
#define lint long long
#define mod 998244353
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define debug(x) cerr<<#x<<"="<<x
#define sp <<" "
#define ln <<endl
using namespace std;
inline int inn()
{
    int x,ch;while((ch=gc)<'0'||ch>'9');
    x=ch^'0';while((ch=gc)>='0'&&ch<='9')
        x=(x<<1)+(x<<3)+(ch^'0');return x;
}
int dp[N][M],a[N],v[N][M],cnt[M],P[N][N],mol[M][N],nxt[M];
int main()
{
    int n=inn(),m=inn(),ans=0,cnt=0;dp[0][m]=1,v[0][m]=1,a[0]=m+1;
    rep(i,0,n) P[i][0]=1;rep(i,1,n) a[i]=inn();sort(a+1,a+n+1);
    rep(i,1,n) rep(j,1,i) P[i][j]=P[i][j-1]*(i-j+1ll)%mod;
    rep(i,1,n/2) swap(a[i],a[n-i+1]);nxt[0]=n+1;
    rep(i,0,m) rep(j,1,n) mol[i][j]=(i>=a[j]?mol[i-a[j]][j]:i);
    rep(i,1,m)
    {
        int &x=nxt[i];x=nxt[i-1];
        while(x>1&&i>=a[x-1]) x--;
    }
    if(nxt[m]>n) return !printf("%d\n%d\n",m,P[n][n]);
    rep(i,0,n-1) rep(j,0,a[i]-1) if(v[i][j]) rep(k,nxt[j],n)
        (dp[k][mol[j][k]]+=(lint)P[n-i][nxt[j]-i-1]*P[n-nxt[j]][k-nxt[j]]%mod*dp[i][j]%mod)%=mod,v[k][mol[j][k]]=1;
    for(int i=a[n]-1;i>=0;i--) rep(j,1,n) if(v[j][i]) { ans=i;goto loop; }
    loop:printf("%d\n",ans);rep(i,1,n) (cnt+=(lint)dp[i][ans]*P[n-i][n-i]%mod)%=mod;
    return !printf("%d\n",cnt);

}

猜你喜欢

转载自blog.csdn.net/mys_c_k/article/details/80807113