Educational Codeforces Round 81 (题解)

第一次AK div2,有点小开心。

A. Display The Number

题目大意:
n n n个火柴棍,求能组成的最大的数字是多少。
解题思路:
贪心,如果是奇数那就第一位为7后面都是1,否则全是1.

#include
using namespace std;
int main()
{
    int T;cin>>T;
    while(T--){
        int n; cin>>n;
        if(n&1){
            if(n == 1) printf("1"), n = 0;
            else n -= 3, printf("7");
        }
        while(n) printf("1"), n -= 2;
        printf("\n");
    }
}

B. Infinite Prefixes

题目大意:
给一个01串 s s s,无穷个串 s s s组成串 t t t,求 t t t有多少个前缀使得串中balance = 0的个数-1的个数= x x x
解题思路:
先求出整个 s s s的balance,然后对于 s s s的每个前缀判断一下是否有可能在加上若干个 s s s之后 b a l a n c e = x balance=x balance=x。要特判 s s s的balance等于0的情况。

#include
using namespace std;
const int maxn = 1e5 + 50;
char s[maxn];
int n, x;
int main()
{
    int T; cin>>T;
    while(T--){
        scanf("%d%d", &n, &x); scanf("%s", s);
        int ans = 0, g = 0;
        for(int i = 0; i < n; ++i){
            if(s[i] == '0') g++;
            else g--;
        }
        int cur = 0;
        if(g == 0){
            if(x == 0) {
                cout<<-1<<endl; continue;
            }
            for(int i = 0; i < n; ++i){
                if(s[i] == '0') cur++;
                else cur--;
                if(cur == x){ans = -1; break;}
            }
            cout<<ans<<endl; continue;
        }
        else{
            if(cur == x) ans++;
            for(int i = 0; i < n; ++i){
                if(s[i] == '0') cur++;
                else cur--;
                if((x-cur)%g == 0 && ((x-cur)/g >= 0) ) ans++;
            }
            cout<<ans<<endl;
        }
    }
}

C. Obtain The String

题目大意:
给两个串 s s s t t t,每次操作从 s s s中取出一个子序列消掉 t t t的某前缀。问最少几次操作把 t t t消完。
解题思路:
直接贪心的消除就好,每次匹配到没有能匹配的就从头开始并让答案加1.

#include
#define ll long long
using namespace std;
const int maxn = 1e5 + 50;
int nxt[maxn][26];
char s[maxn];
char t[maxn];
int main()
{
    int T;cin>>T;
    while(T--){
        scanf("%s", s+1);
        int n = strlen(s+1);
        scanf("%s", t+1);
        int len = strlen(t+1);
        for(int i = 0; i < 26; ++i) nxt[n][i] = 0;
        for(int i = n-1; i >= 0; --i){
            for(int j = 0; j < 26; ++j) nxt[i][j] = nxt[i+1][j];
            nxt[i][s[i+1]-'a'] = i+1;
        }
        int ans = 1;
        int cur = 0;
        for(int i = 1; i <= len; ++i){
            int x = t[i]-'a';
            if(!nxt[0][x]){
                ans = -1;break;
            }
            if(nxt[cur][x]){
                cur = nxt[cur][x];
            }else{
                cur = nxt[0][x];
                ans++;
            }
        }
        cout<<ans<<endl;
    }
}

D. Same GCDs

题目大意:
给出 a a a m m m,求满足 g c d ( a , m ) = g c d ( a + x , m ) gcd(a,m)=gcd(a+x,m) gcd(a,m)=gcd(a+x,m) x ∈ [ 0 , m − 1 ] x\in [0,m-1] x[0,m1] x x x的个数。
a , m ≤ 1 0 10 a,m\le10^{10} a,m1010
解题思路:
p = g c d ( a , m ) p=gcd(a,m) p=gcd(a,m),易得 x x x p p p的倍数。设 a = u ∗ p , m = v ∗ p a=u*p, m=v*p a=up,m=vp x = x ′ ∗ p x=x'*p x=xp,问题转换成了 [ u , u + m p − 1 ] [u,u+\frac{m}{p}-1] [u,u+pm1]范围内有多少个数字与 m p \frac{m}{p} pm互质。
m p \frac{m}{p} pm的质因子提出来之后容斥求一下就好了。

#include
#define ll long long
#define lowbit(x) ((x)&(-(x)))
using namespace std;
vector<ll> v;
int bin[1<<20];
ll work(ll a){
    ll ans = a;
    int n = v.size();
    for(int mask = 1; mask < (1<<n); ++mask){
        ll t = 1;
        for(int i = 0; i < n; ++i) if(mask>>i&1) t*=v[i];
        ll f = 1; if(bin[mask]&1) f = -1;
        ans += f*(a/t);
    }return ans;
}
ll sol(ll a, ll b){
    v.clear();
    ll t = b;
    for(ll p = 2; p*p <= b; ++p){
        if(t%p == 0) v.push_back(p);
        while(t%p == 0) t/= p;
    }
    if(t != 1) v.push_back(t);
    return work(a+b-1)-work(a)+1;
}
int main()
{
    for(int i = 1; i < (1<<20); ++i) bin[i] = bin[i-lowbit(i)]+1;
    int T; cin>>T;
    while(T--){
        ll a, m;
        cin>>a>>m;
        ll p = __gcd(a, m);
        a/=p; m/= p;
        cout<<sol(a, m)<<endl;
    }
}

E. Permutation Separation

题目大意:
给你一个排列,每个排列中的第 i i i个数字为 p i p_i pi,权值为 w i w_i wi。你可以选择一个位置切一刀把它分成左右两部分,然后你可以花费 w i w_i wi p i p_i pi移动到另一个部分。最终要保证左半部分的最大值小于右半部分的最小值。
解题思路:
先暴力的想:可以先枚举切的位置,再枚举右半部分的最小值是什么,这样我们有了一个 n 2 n^2 n2的算法,考虑优化这个算法。
从大到小枚举切的位置的时候,每次切的位置左移一位,相当于在右边加入了一个 p i p_i pi,在左边去掉了一个 p i p_i pi。那么右边最小值为 [ p i + 1 , n ] [p_i+1,n] [pi+1,n]的这些答案都增加了 w i w_i wi,为 [ 1 , p i ] [1,p_i] [1,pi]的这些答案减少了 w i w_i wi
可以用线段树进行区间加法,维护区间最小值来维护答案。

#include
#define ll long long
#define lowbit(x) ((x)&(-(x)))
#define mid ((l+r)>>1)
#define lson rt<<1, l, mid
#define rson rt<<1|1, mid+1, r
using namespace std;
const int maxn = 2e5 + 50;
int p[maxn];
ll w[maxn];
ll lz[maxn<<2], mi[maxn<<2];
void down(int rt){
    lz[rt<<1] += lz[rt];
    mi[rt<<1] += lz[rt];
    lz[rt<<1|1] += lz[rt];
    mi[rt<<1|1] += lz[rt];
    lz[rt] = 0;
    return;
}
void up(int rt){
    mi[rt] = min(mi[rt<<1], mi[rt<<1|1]);
}
void update(int rt, int l, int r, int L, int R, ll x){
    if(L <= l && r <= R) {
        lz[rt] += x;
        mi[rt] += x;
        return;
    }down(rt);
    if(L <= mid) update(lson, L, R, x);
    if(R > mid) update(rson, L, R, x);
    up(rt);
}
int n;
ll sol(){
    for(int i = 1; i < n; ++i){
        update(1, 0, n, 0, p[i], w[i]);
    }
    if(p[n] != n) update(1, 0, n, p[n]+1, n, w[n]);
    ll ans = mi[1];
    for(int i = n-1; i > 1; --i){
        if(p[i] != n) update(1, 0, n, p[i]+1, n, w[i]);
        update(1, 0, n, 0, p[i], -w[i]);
        ans = min(ans, mi[1]);
    }return ans;
}
int main()
{
	scanf("%d", &n);
	for(int i = 1; i <= n; ++i){
        scanf("%d", &p[i]);
	}
	for(int i = 1; i <= n; ++i) scanf("%lld", &w[i]);
	//reverse(p+1,p+1+n); reverse(w+1,w+1+n);
	ll ans = sol();
	ans = min(w[1], ans);
	ans = min(w[n], ans);
	cout<<ans<<endl;
}

F. Good Contest

题目大意:
n n n个数字,第 i i i个数字随机分布在 [ l i , r i ] [l_i,r_i] [li,ri]。求最后的序列单调不升的概率。
n ≤ 50 n\le 50 n50
解题思路:
这题是Camp Day6的D题弱化版……
见2020 CCPC Wannafly Winter Camp Day6 D. 递增递增(DP)
这题思路就是把区间离散化之后 f [ i ] [ j ] f[i][j] f[i][j]表示前 i i i个区间放了 j j j个数字的方案数。然后除以总方案数就是答案。
小号白嫖F题

#include
#define ll long long
using namespace std;
/*注意爆long long*/
const int maxn = 205;
ll cc[200];
int num = 0;
int n;
ll l[55], r[55];
const ll mod = 998244353;
ll dp[200][200];
ll f[200][200];
ll fac[maxn], ifac[maxn], inv[maxn];
ll qm(ll a, ll b){ll res = 1; while(b) {if(b&1) res=res*a%mod; a = a*a%mod; b>>=1;} return res;}
ll Choose(ll a, ll b){//a个可选的数字,b个要放的数 C(b+a-1, b)
    ll res = 1;
    for(ll i = a; i < b+a; ++i) res = i%mod*res%mod;
    for(ll i = 2; i <= b; ++i)  res = res*inv[i]%mod;
    return res;
}
int main()
{
    fac[0] = ifac[0] = 1;
    for(int i = 1; i < maxn; ++i) fac[i] = fac[i-1]*i%mod, ifac[i] = qm(fac[i], mod-2), inv[i] = qm(i, mod-2);
    cin>>n;
    ll fm = 1;
    for(int i = 1; i <= n; ++i){
        scanf("%lld%lld", &l[i], &r[i]); fm = fm*(r[i]-l[i]+1)%mod;
    }
    reverse(l+1,l+1+n);
    reverse(r+1,r+1+n);
    for(int i = 1; i <= n; ++i){
        l[i] = max(l[i], l[i-1]);
        cc[++num] = l[i];
    }
    cc[++num] = r[n]+1;
    for(int i = n-1; i >= 1; --i){
        r[i] = min(r[i], r[i+1]);
        cc[++num] = r[i]+1;
    }
    sort(cc+1,cc+1+num);
    num = unique(cc+1,cc+1+num)-cc-1;
    ll inv2 = (mod+1)/2;
    f[0][0] = 1;
    for(int i = 1; i < num; ++i){
        f[i][0] = 1;
        ll L = cc[i], R = cc[i+1]-1;
        for(int j = 1; j <= n; ++j){
            f[i][j] = f[i-1][j];
            for(int k = j; k > 0; --k){
                if(l[k] <= L && R <= r[k]){
                        f[i][j] = (f[i][j] + f[i-1][k-1]*Choose(R-L+1, j-k+1)%mod)%mod;
                }else break;
            }
        }
    }
    ll ans = f[num-1][n];
    ans = ans*qm(fm, mod-2)%mod;
    //assert(ans > 0);
    cout<<ans<<endl;
}
/*
2
1 2
3 4
*/

你可能感兴趣的:(练习)