The Preliminary Contest for ICPC Asia Shanghai 2019

时间:2019-09-18
本文章向大家介绍The Preliminary Contest for ICPC Asia Shanghai 2019,主要包括The Preliminary Contest for ICPC Asia Shanghai 2019使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

传送门

B. Light bulbs

题意:
起初\(n\)个位置状态为\(0\)\(m\)次操作,每次操作更换区间状态:\(0\)\(1\)\(1\)\(0\)
共有\(T,T\leq 1000\)组数据,\(n\leq 10^6,m\leq 1000\)
最后输出状态为\(1\)的个数和。

思路:
一开始冲了一发维护差分,最后求出前缀和,成功\(TLE\)
其实只要发现有用的点只有\(2m\)个,之后从左到右扫一遍,类似于扫描线的思想,累加贡献就行了。


Code

#include <bits/stdc++.h>
#define MP make_pair
#define fi first
#define se second
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
const int N = 1e3 + 5;

int T;
int n, m;

struct Istream {
    template <class T>
    Istream &operator >>(T &x) {
        static char ch;static bool neg;
        for(ch=neg=0;ch<'0' || '9'<ch;neg|=ch=='-',ch=getchar());
        for(x=0;'0'<=ch && ch<='9';(x*=10)+=ch-'0',ch=getchar());
        x=neg?-x:x;
        return *this;
    }
}fin;

pii x[N << 1];

int main() {
    fin >> T;
    int tot = 0;
    while(T--) {
        fin >> n >> m;
        for(int i = 1; i <= m; i++) {
            int l, r; fin >> l >> r;
            x[2 * i - 1] = MP(l, 0), x[2 * i] = MP(r + 1, 1);
        }
        sort(x + 1, x + 2 * m + 1, [&](pii A, pii B) {
            if(A.fi == B.fi) return A.se < B.se;
            return A.fi < B.fi;
        });
        int cnt = 0, ans = 0, last = 0;
        for(int i = 1; i <= 2 * m; i++) {
            if(x[i].se == 0) {
                if(cnt & 1) {
                    ans += x[i].fi - last;
                }
                ++cnt;
            } else{
                if(cnt & 1) {
                    ans += x[i].fi - last;
                }
                --cnt;
            }
            last = x[i].fi;
        }
        ++tot;
        cout << "Case #" << tot << ": ";
        printf("%d\n", ans);
    }
    return 0;
}

C. Triple

题意:
给出三个数组\(A,B,C\),要求满足条件的三元组\((i,j,k)\),满足:
\[ \left\{ \begin{aligned} |A_i-B_j|\leq C_k\\ |B_j-C_k|\leq A_i\\ |A_i-C_k|\leq B_j \end{aligned} \right. \]
限制条件:\(n\leq 10^5,1\leq A_i,B_i,C_i\leq 10^5\)

思路:

  • 数据范围较小时,可以考虑枚举一个\(i\)作为最大值,然后其余两个数组双指针来扫,时间复杂度\(O(3*n^2)\)
  • 但这对于本题数据来说,显然会\(T\)的,考虑如何优化。
  • 注意到在多项式乘法当中,两项相乘其指数相加,那么我们可以将数组对应挂在指数上面,系数则为对应指数出现次数,那么将两个多项式乘起来最终得到一项\(x^n\),其前面的系数则为两数相加和为\(n\)的方案数。
  • 此过程\(FFT\)优化即可,时间复杂度\(O(nlogn)\)

因为本题数据比较特殊,所以就小范围暴力,大范围\(FFT\)优化即可。
另外还注意一些细节,去重的问题:

  • 如果出现多个都为最大值的情况,会重复计算。
  • 小范围暴力时,可以参考我的代码,只是有些地方限制一下即可。
  • \(FFT\)处理时,将三个数组丢到同一个数组当中进行排序,然后扫一遍,对于第\(i\)个位置,假设目前其为最大值。那么我们减掉一下情况就行(以\(a_i\)为最大值为例):
    • \(b_j\geq a_i,c_k\geq a_i\)
    • \(b_j\geq a_i,c_k<a_i\)
    • \(b_j<a_i,c_k\geq a_i\)
  • 所以用个桶维护每种数出现次数即可。

详见代码(有点复杂...)


Code

#include <bits/stdc++.h>
#define MP make_pair
#define fi first
#define se second
#define sz(x) (int)(x).size()
//#define Local
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
const int N = 3e5 + 5;
int UP = 200000;
const double pi = acos(-1.0);
struct Com{
    double x, y;
    Com(double xx = 0, double yy = 0) {x = xx, y = yy;}
}a[N], b[N], c[N];
Com operator + (const Com &a, const Com &b) {return Com(a.x + b.x, a.y + b.y);}
Com operator - (const Com &a, const Com &b) {return Com(a.x - b.x, a.y - b.y);}
Com operator * (const Com &a, const Com &b) {return Com(a.x * b.x - a.y * b.y, a.x * b.y + a.y * b.x);}
int l, r[N];
int lim = 1;
void solve(Com *A, int type) {
    for(int i = 0; i < lim; i++) if(i < r[i]) swap(A[i], A[r[i]]);
    for(int mid = 1; mid < lim; mid <<= 1) {
        Com Wn(cos(pi / mid), type * sin(pi / mid)) ;
        for(int R = mid << 1, j = 0; j < lim; j += R) {
            Com w(1, 0);
            for(int k = 0; k < mid; k++, w = w * Wn) {
                Com x = A[j + k], y = w * A[j + mid + k];
                A[j + k] = x + y;
                A[j + mid + k] = x - y;
            }
        }
    }
}
int n;
void FFT(Com *a, Com *b) {
    solve(a, 1); solve(b, 1);
    for(int i = 0; i <= lim; i++) c[i] = a[i] * b[i];
    solve(c, -1);
    for(int i = 0; i <= UP; i++) c[i].x = (c[i].x / lim + 0.5);
}

int arr[3][N];
ll sum[N];

ll gao(int p, int q, int r) {
    vector <int> num1(lim + 1), num2(lim + 1);
    for(int i = 1; i <= n; i++) {
        ++num1[arr[p][i]];
        ++num2[arr[q][i]];
    }
    for(int i = 0; i <= lim; i++) {
        a[i] = Com(num1[i]);
        b[i] = Com(num2[i]);
    }
    FFT(a, b);
    for(int i = UP; i >= 0; i--)
        sum[i] = sum[i + 1] + (ll)c[i].x;
    ll ans = 0;
    for(int i = 1; i <= n; i++) {
        ans += sum[arr[r][i]];
    }
    return ans;
}
pii tmp[N];
int cnt[3];
ll calc(int id){
    ll ans = 0;
    int x = (id + 1) % 3;
    int y = (x + 1) % 3;
    ans += 1ll * cnt[x] * (n - cnt[y]);
    ans += 1ll * cnt[y] * (n - cnt[x]);
    ans += 1ll * (n - cnt[x]) * (n - cnt[y]);
    return ans;
}
ll work2() {
    int tot = 0;
    for(int i = 0; i < 3; i++) {
        for(int j = 1; j <= n; j++) {
            cin >> arr[i][j];
            tmp[++tot] = MP(arr[i][j], i);
        }
    }
    ll ans = 0;
    ans += gao(0, 1, 2);
    ans += gao(1, 2, 0);
    ans += gao(0, 2, 1);
    cnt[0] = cnt[1] = cnt[2] = 0;
    sort(tmp + 1, tmp + tot + 1);
    for(int i = 1; i <= tot; i++) {
        ans -= calc(tmp[i].se);
        ++cnt[tmp[i].se];
    }
    return ans;
}
namespace bf {
    int getans(int *a, int *b, int *c, int d, int e) {
        int ans = 0;
        for(int i = 1; i <= n; i++) {
            int k = upper_bound(c + 1, c + n + 1, a[i] - e) - c - 1;
            int p = k;
            for(int j = 1; j <= n && b[j] <= a[i] - d; j++) {
                while(k && b[j] + c[k] >= a[i]) --k;
                ans += p - k;
            }
        }
        return ans;
    }
    int A[N], B[N], C[N];
    int work() {
        for(int i = 1; i <= n; i++) cin >> A[i];
        for(int i = 1; i <= n; i++) cin >> B[i];
        for(int i = 1; i <= n; i++) cin >> C[i];
        sort(A + 1, A + n + 1);
        sort(B + 1, B + n + 1);
        sort(C + 1, C + n + 1);
        int ans = 0;
        ans += getans(A, B, C, 0, 0);
        ans += getans(B, A, C, 1, 0);
        ans += getans(C, A, B, 1, 1);
        return ans;
    }
}

int Case;
void run() {
    ++Case;
    cin >> n;
    ll ans;
    if(n <= 2000) {
        ans = bf :: work();
    } else {
        ans = work2();
    }
    cout << "Case #" << Case << ": ";
    cout << ans << '\n';
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);
    cout << fixed << setprecision(20);
#ifdef Local
    freopen("../input.in", "r", stdin);
    freopen("../output.out", "w", stdout);
#endif
    while(lim <= UP) lim <<= 1, l++;
    for(int i = 0; i < lim; i++) {
        r[i] = (r[i >> 1] >> 1) | ((i & 1) << (l - 1));
    }
    int t; cin >> t;
    while(t--) run();
    return 0;
}

D. Counting Sequences I

题意:
定义一个好的序列\(a_1,a_2,\cdots,a_n\)满足:\(n\geq 2\)\(a_1+a_2+\cdots+a_n=a_1\cdot a_2\cdot \cdots a_n\)
给定一个长度\(n\),问有多少长度为\(n\)的序列满足是好的序列。

思路:

  • 注意到\(1\)在等式里面比较特殊,因为不断增加\(1\)的个数,那么等式左边会不断增加,而等式右边则会不变。
  • 因为题目给出的\(n\)较小,所以直接爆搜即可,但要注意剪枝:两边的差已经不能用\(1\)弥补时就及时中止。

代码中爆搜的时候并没有考虑\(1\),而是默认用\(1\)来补差值。同时还要维护一些分母上的东西用来统计答案。
详见代码吧:


Code

#include<bits/stdc++.h>
typedef long long ll;
typedef unsigned long long ull;
typedef double db;
const int MAXN = 3e3+5,MAXM = 2e6+5,MOD = 1e9+7,INF = 0x3f3f3f3f;
const ll INFL = 0x3f3f3f3f3f3f3f3f;
const db eps = 1e-9;
#define lson o<<1,l,m
#define rson o<<1|1,m+1,r
#define mid l + ((r-l)>>1)
#define rep(i,a,b) for(register int i=(a);i<=(b);i++)
#define pii pair<int,int>
#define vii vector<pii>
#define vi vector<int>
using namespace std;

int t,n,ans,a;
ll fact[MAXN],inv[MAXN];
ll qpow(ll a,ll b){
    ll ans=1;
    for(;b;b>>=1,a=a*a%MOD)if(b&1)ans=ans*a%MOD;
    return ans;
}
inline void add(int &x,int y){
    x+=y;
    if(x>=MOD)x-=MOD;
}
void dfs(int p,ll A,ll B,int pre,ll sum,int cnt){
    if(p>n){
        if(A==B){
            sum=sum*inv[cnt]%MOD;
            ans += fact[p-1]*sum%MOD;
            ans%=MOD;
        }
        return ;
    }else if(p>1 && A-B == n-p+1){
        sum=sum*inv[cnt]%MOD*inv[n-p+1]%MOD;
        ans += fact[n]*sum%MOD;
        ans%=MOD;
    }
    for(int i=pre;i<=3000;i++){
        if((ll)A*i - (B+i)>n)break;
        if(i==pre) dfs(p+1,A*i,B+i,i,sum,cnt+1);
        else dfs(p+1,A*i,B+i,i,sum*inv[cnt]%MOD,1);
    }
}
int main(){
    ios::sync_with_stdio(false);

    fact[0]=1;
    for(int i=1;i<=3000;i++)fact[i]=fact[i-1]*i%MOD;
    inv[3000] = qpow(fact[3000],MOD-2);
    for(int i=3000-1;i>=0;i--)inv[i]=inv[i+1]*(i+1)%MOD;

    cin>>t;
    while(t--){
        cin>>n;
        ans=0;
        dfs(1,1,0,2,1,0);
        cout<<ans<<'\n';
    }
    return 0;
}

E. Counting Sequences II

题意:
给出限制条件\(n,m\),构造序列\(a_i\),满足\(1\leq a_i\leq m\)对于\(1\leq i\leq n\),并满足偶数的个数为偶数。

思路:

  • 对于一个偶数,其出现次数可能为\(0,2,4,\cdots\);对于一个奇数,其出现次数可能为\(0,1,2,3,\cdots\)
  • 因为所求情况数为排列,考虑指数生成函数,我们将每个数可能出现的次数挂到指数上,系数则为方案数。
  • 易知对于一个偶数,其生成函数为:\(1+\frac{x^2}{2!}+\frac{x^4}{4!}+\cdots\);对于一个奇数,其生成函数为\(1+x+\frac{x^2}{2!}+\frac{x^3}{3!}+\frac{x^4}{4!}+\cdots\)
  • 因为小于等于\(m\)的数有\(\lfloor\frac{m}{2}\rfloor\),记其为\(t\),那么答案就为\((\frac{e^x+e^{-x}}{2})^te^{x(m-t)}\)的第\(n\)项的系数乘以\(n!\)
  • 最后随便化一下,把\((\frac{e^x+e^{-x}}{2})^t\)展开,就得到:\(\frac{1}{2^t}\sum_{i=0}^t {t\choose i}e^{(m-2i)x}\),其第\(n\)项系数为\(\frac{1}{2^t}\sum_{i=0}^t (m-2i)^n\)

预处理一下阶乘和逆元就行了。
总结一下,关键还是第一步构造生成函数的思想,对每个数独立考虑所有的情况,其余的都很好推。
生成函数nb!多项式nb!


Code

#include <bits/stdc++.h>
#define MP make_pair
#define fi first
#define se second
#define sz(x) (int)(x).size()
//#define Local
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
const int N = 2e5 + 5, MOD = 1e9 + 7;
ll qpow(ll a, ll b) {
    ll ans = 1;
    while(b) {
        if(b & 1) ans = ans * a % MOD;
        a = a * a % MOD;
        b >>= 1;
    }
    return ans;
}
ll n, m;
int fac[N], inv[N];

int C(ll n, ll m) {
    return 1ll * fac[n] * inv[m] % MOD * inv[n - m] % MOD;
}

void init() {
    fac[0] = 1;
    for(int i = 1; i < N; i++) fac[i] = 1ll * fac[i - 1] * i % MOD;
    inv[N - 1] = qpow(fac[N - 1], MOD - 2);
    for(int i = N - 2; i >= 0; i--) inv[i] = 1ll * inv[i + 1] * (i + 1) % MOD;
}

void run() {
    cin >> n >> m;
    int t = m >> 1;
    ll ans = 0;
    int fm = qpow(qpow(2, t), MOD - 2);
    for(int i = 0; i <= t; i++) {
        ans = (ans + 1ll * C(t, i) * qpow((m - 2 * i), n) % MOD) % MOD;
    }
    if(ans < 0) ans += MOD;
    ans = ans * fm % MOD;
    cout << ans << '\n';
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);
    cout << fixed << setprecision(20);
#ifdef Local
    freopen("../input.in", "r", stdin);
    freopen("../output.out", "w", stdout);
#endif
    init();
    int t; cin >> t;
    while(t--) run();
    return 0;
}

F. Rhyme scheme

题意:
求字典序第\(k\)大的序列,该序列满足每一位不会大于前面的最大位加一。

思路:

  • 因为是字典序第\(k\)小,所以考虑逐位确定,但是每当我们考虑某一位时我们需要知道后缀个数信息。
  • 考虑\(dp\)(递推)(我也不知道是什么)来计算这个信息。
  • 显然每一位与之相关的有当前位数和前面最大的位数,同时由于与后缀相关,我们考虑从后往前依次考虑不同长度的后缀。
  • \(dp[n,i,j]\)表示长度为\(n\)的后缀,当前从后往前第\(i\)位,前面最大值为\(j\)时的方案数,那么就有转移方程:

\[ dp[n][i][j]=dp[n][i+1][j]*j+dp[n][i+1][j+1] \]

之后逐位确定就行。


Code

#include<cstdio>
#include<cmath>
#include<cctype>
#include<algorithm>
#include<vector>
#include<cstring>
#include<cassert>
using namespace std;
#define REP(r,x,y) for(register int r=(x); r<y; r++)
#define REPE(r,x,y) for(register int r=(x); r<=y; r++)
#ifdef sahdsg
#define DBG(...) printf(__VA_ARGS__)
#else
#define DBG(...) (void)0
#endif
template <class T>
inline void read(T&x) {
    static int si=1; static char ch; x=0;
    do ch=getchar(); while(!isdigit(ch) && ch!='-');
    if(ch=='-') si=-1, ch=getchar();
    while(isdigit(ch)) {x=x*10+ch-'0'; ch=getchar();} x*=si;
}
template <class T, class... A> inline void read(T&t, A&...a){read(t); read(a...);}
struct Istream {
    template <class T>
        Istream &operator >>(T &x) {
            static char ch;static bool neg;
            for(ch=neg=0;ch<'0' || '9'<ch;neg|=ch=='-',ch=getchar());
            for(x=0;'0'<=ch && ch<='9';(x*=10)+=ch-'0',ch=getchar());
            x=neg?-x:x;
            return *this;
        }
}fin;

struct Ostream {
    template <class T>
        Ostream &operator <<(T x) {
            x<0 && (putchar('-'),x=-x);
            static char stack[233];static int top;
            for(top=0;x;stack[++top]=x%10+'0',x/=10);
            for(top==0 && (stack[top=1]='0');top;putchar(stack[top--]));
            return *this;
        }

    Ostream &operator <<(char ch) {
        putchar(ch);
        return *this;
    }
}fout; 
#define MAXN 1000007
#define LL __int128
int n;
LL f[30][30][30];
LL k;
inline void db() {
    memset(f,0,sizeof f);
    for(int l=26;l>=1;l--) {
        REPE(i,1,26) f[i][1][l]=1;
        REPE(j,2,26) {
            REPE(i,1,26) {
                REPE(k,1,max(i+1,l)) {
                    f[i][j][l]+=f[k][j-1][max(i+1,l)];
                }
            }
        }
    }
}
char ans[30];
inline void calc(int x, int d, LL sum) {
    if(d==0) return;
    REPE(i,1,x+1) {
        sum+=f[max(i,x)][d][x];
        if(sum>=k) {
            ans[d]=i+'A'-1;
            putchar(ans[d]);
            calc(max(i,x),d-1,sum-f[max(i,x)][d][x]);
            return;
        }
    }
}
int main() {
    db(); DBG("%d\n", f[1][2][9]);
    int kase=0;
    int T; read(T);
    while(0<T--) {
        read(n); fin>>k;
        printf("Case #%d: ", ++kase);
        calc(1,n,0);
        putchar('\n');
    }
    return 0;
}

G. Substring

题意:
现定义两个字符串相等:

  • \(S_1=T_1,S_n=T_n\)
  • 每个字母出现次数相等。

现在给出串\(S\),然后\(n\)个串,对于每个串,回答它与多少\(S\)的子串相等。串的长度之和不超过\(10^5\)

思路:

  • 首先思考如何判断两个串相等,因为与顺序无关,所以我们可以换一下哈希的方法,使得哈希值只与字母以及出现次数有关。
  • 具体地说就是\(hash[i]=hash[i-1]+p[s[i]-'a']\)\(p[k]=p^k\)
  • 因为\(n\)可能很大,对于每个串直接回答会超时。如果只有一个询问的话,因为长度固定,所以可以考虑类似于滑动窗口那样,那么对于一种长度\(O(n)\)就可以求解。
  • 注意到虽然有多组询问,但是询问串的长度总和不超过\(10^5\),也就是说长度不同的串为\(\sqrt{10^5}\)个,考虑时限比较宽松,那么直接枚举长度搞就行。

我实现是用了两个unordered_map,最后两个相减得到答案,详细见代码吧:


Code

#include <bits/stdc++.h>
#define MP make_pair
#define fi first
#define se second
#define sz(x) (int)(x).size()
//#define Local
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
const int N = 1e5 + 5;

unordered_map <ull, int> mp[26][26], mp2[26][26];

char s[N];
int n;
ull p[26];
int ans[N], tmp[N];

void init() {
    p[0] = 131;
    for(int i = 1; i < 26; i++) p[i] = p[i - 1] * (ull)131;
}

ull Hash(const string &s) {
    int len = s.length();
    ull ans = 0;
    for(int i = 0; i < len; i++) {
        ans += p[s[i] - 'a'];
    }
    return ans;
}

void gao(int x) {
    int len = strlen(s + 1);
    ull val = 0;
    if(len < x) return;
    for(int i = 1; i <= x; i++) {
        val += p[s[i] - 'a'];
    }
    if(mp2[s[1] - 'a'][s[x] - 'a'].count(val)) ++mp[s[1] - 'a'][s[x] - 'a'][val];
    for(int i = x + 1; i <= len; i++) {
        val += p[s[i] - 'a'];
        val -= p[s[i - x] - 'a'];
        if(mp2[s[i - x + 1] - 'a'][s[i] - 'a'].count(val)) ++mp[s[i - x + 1] - 'a'][s[i] - 'a'][val];
    }
}

void run() {
    cin >> s + 1;
    cin >> n;
    string t;
    vector <pair<string, int> > q;
    for(int i = 0; i < 26; i++) {
        for(int j = 0; j < 26; j++) {
            mp[i][j].clear();
            mp2[i][j].clear();
        }
    }
    for(int i = 1; i <= n; i++) {
        cin >> t;
        int lent = t.length();
        int fir = t[0] - 'a', last = t[lent - 1] - 'a';
        ++mp[fir][last][Hash(t)];
        ++mp2[fir][last][Hash(t)];
        tmp[i] = lent;
        q.push_back(MP(t, i));
    }
    sort(tmp + 1, tmp + n + 1);
    tmp[n + 1] = 0;
    for(int i = 1, j = 1; i <= n; i = j) {
        while(tmp[i] == tmp[j]) ++j;
        gao(tmp[i]);
    }
    for(auto &it : q) {
        string t = it.fi;
        int id = it.se;
        int lent = t.length();
        ull val = Hash(t);
        ans[id] = mp[t[0] - 'a'][t[lent - 1] - 'a'][val] - mp2[t[0] - 'a'][t[lent - 1] - 'a'][val];
    }
    for(int i = 1; i <= n; i++) cout << ans[i] << '\n';
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);
    cout << fixed << setprecision(20);
#ifdef Local
    freopen("../input.in", "r", stdin);
    freopen("../output.out", "w", stdout);
#endif
    init();
    int t; cin >> t;
    while(t--) run();
    return 0;
}

J. Stone game

题意:
现在有\(n\)个石头,每个石头都有权值\(a_i\)
现在要拿石子,直到满足下面的条件:目前手中石子的权值和大于等于剩下石子的权值和,并且拿掉任意一块石子,手中石子的权值和就小于剩下石子的权值和了。
问这样拿石子的方案有多少种。

思路:

  • 注意到最终去掉的石子一定为权值最小的一块,那么我们可以考虑枚举最小权值的石子,然后再来\(dp\)
  • 定义\(dp[i][j]\)表示前\(i\)个石子,权值和为\(j\)的方案数,那么考虑当前石子拿或者不拿直接\(dp\)就行了,很简单的计数\(dp\)
  • 但是这样搞复杂度是\(O(n^3)\)的,显然不能接受。
  • 发现我们直接这样搞会计算很多重复的部分,比如前面的很多东西都会重复计算,并且我们每次计算的其实是一个后缀。那么直接将\(dp\)改为从后往前\(dp\)就行了:最小权值每减少一次,\(dp\)就会多计算一个,这样复杂度就为\(O(n^2)\)了。

注意最后要特判一下最后一个位置。


Code

#include <bits/stdc++.h>
#define MP make_pair
#define fi first
#define se second
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
const int N = 305, MOD = 1e9 + 7, MAX = 150005;

int T;
int n, m;
int a[N], sum[N];
int dp[2][MAX];

void add(int &x, int y) {
    x += y;
    if(x >= MOD) x -= MOD;
}

int main() {
//    freopen("input.in", "r", stdin);
    ios::sync_with_stdio(false); cin.tie(0);
    scanf("%d", &T);
    while(T--) {
        sum[0] = 0;
        scanf("%d", &n);
        for(int i = 1; i <= n; i++) scanf("%d", &a[i]);
        sort(a + 1, a + n + 1);
        for(int i = 1; i <= n; i++) sum[i] = sum[i - 1] + a[i];
        int ans = 0;
        for(int i = 0; i <= 1; i++) {
            for(int j = 0; j <= sum[n]; j++)
                dp[i][j] = 0;
        }
        dp[0][0] = 1;
        for(int i = n, cur = 1; i >= 2; i--, cur ^= 1) {
            for(int k = 0; k <= sum[n]; k++) dp[cur][k] = dp[cur ^ 1][k];
            for(int k = 0; k <= sum[n] - sum[i - 1]; k++) {
                if(k >= a[i]) add(dp[cur][k], dp[cur ^ 1][k - a[i]]);
            }
            for(int k = 0; k <= sum[n] - sum[i - 1]; k++) {
//                cout << i << ' ' << k << ' ' << dp[cur][k] << '\n';
                if(2 * (k + a[i - 1]) >= sum[n] && 2 * k + a[i - 1] <= sum[n])
                    add(ans, dp[cur][k]);
            }
//            for(int k = 0; k <= sum[n] - sum[i - 1]; k++) dp[cur ^ 1][k] = dp[cur][k];
        }
        if(2 * a[n] >= sum[n] && a[n] <= sum[n]) ++ans;
        printf("%d\n", ans);
    }
    return 0;
}

L. Digit sum

数位\(dp\)板子,或者直接暴力==


Code

#include<bits/stdc++.h>
typedef long long ll;
typedef unsigned long long ull;
typedef double db;
const int MAXN = 1e6+5,MAXM = 2e6+5,MOD = 998244353,INF = 0x3f3f3f3f;
const ll INFL = 0x3f3f3f3f3f3f3f3f;
const db eps = 1e-9;
#define lson o<<1,l,m
#define rson o<<1|1,m+1,r
#define mid l + ((r-l)>>1)
#define rep(i,a,b) for(register int i=(a);i<=(b);i++)
#define pii pair<int,int>
#define vii vector<pii>
#define vi vector<int>
using namespace std;

int t,n,b;
ll f[11][33][11],base[11][22];
void init(){
    for(int b=2;b<=10;b++){
        base[b][0]=1;
        for(int i=1;i<=21;i++)base[b][i]=INF;
        for(int i=1;i<22;i++){
            base[b][i] = base[b][i-1]*b;
            if(base[b][i]>=1000000)break;
        }
    }
    for(int b=2;b<=10;b++){
        for(int i=1;base[b][i-1]<=1000000;i++){
            for(int j=1;j<b;j++){
                f[b][i][j] = f[b][i][0] + j*base[b][i-1];
            }
            for(int j=0;j<b;j++)f[b][i+1][0] += f[b][i][j];
        }
    }
}
int a[22];
ll solve(int n,int b){
    int cnt=0,tmp=n;
    while(tmp){
        a[++cnt] = tmp%b;
        tmp/=b;
    }
    ll ans=0;
    for(int i=cnt;i>=1;i--){
        for(int j=0;j<a[i];j++){
            ans += f[b][i][j];
        }
        n -= a[i]*base[b][i-1];
        ans += a[i]*(n+1);
    }
    return ans;
}
int main(){
    ios::sync_with_stdio(false);
    init();
    cin>>t;
    for(int kase=1;kase<=t;kase++){
        cin>>n>>b;
        cout<<"Case #"<<kase<<": "<<solve(n,b)<<'\n';
    }
    return 0;
}

原文地址:https://www.cnblogs.com/heyuhhh/p/11545443.html