2020杭电多校第三场部分题解(1004, 1005, 1006, 1009)

1004 Tokitsukaze and Multiple

可以处理出每个点i作为划分的右端点,它最近的左端点为L[i],然后问题就转换成了若个个区间,求彼此之间不相交的情况下最多有几个共存。经典问题,这里用DP解决

#include
#define ll long long
#define pb push_back
#define lowbit(x) ((x)&(-(x)))
#define mid ((l+r)>>1)
#define lson rt<<1, l, mid
#define rson rt<<1|1, mid+1, r
#define fors(i, a, b) for(int i = (a); i < (b); ++i)
using namespace std;
const int maxn = 1e5 + 50;
int sum[maxn], a[maxn], last[maxn], L[maxn];
int n, p;
void init(){
    scanf("%d%d", &n, &p);
    fors(i,1,n+1) scanf("%d", &a[i]);
}
int dp[maxn];
void sol(){
    memset(last, -1, sizeof last);
    memset(L, -1, sizeof L);
    last[0] = 0;
    dp[0] = 0;
    sum[0] = 0;
    fors(i,1,n+1){
        sum[i] = (sum[i-1] + a[i])%p;
        if(last[sum[i]] != -1){
            L[i] = last[sum[i]];
        }
        last[sum[i]] = i;
        //cout<<"L:"<
    }
    fors(i,1,n+1){
        if(L[i] == -1) dp[i] = dp[i-1];
        else dp[i] = max(dp[i-1], dp[L[i]]+1);
    }
    printf("%d\n", dp[n]);
}
int main()
{
    int T;cin>>T;
    while(T--){
        init();
        sol();
    }
}

1005 Little W and Contest

很显然要维护并查集,设第i个集合有x个2,y个1.一开始答案很容易得出,然后每次合并集合就减去这两个集合中的人原本可以组合的team个数

#include
#define ll long long
#define pb push_back
#define lowbit(x) ((x)&(-(x)))
#define mid ((l+r)>>1)
#define lson rt<<1, l, mid
#define rson rt<<1|1, mid+1, r
#define fors(i, a, b) for(int i = (a); i < (b); ++i)
using namespace std;
const int maxn = 1e5 + 50;
int fa[maxn];
int fnd(int x){
    if(x == fa[x]) return x;
    return fa[x] = fnd(fa[x]);
}
ll x[maxn], y[maxn];
int n;
int p[maxn];
ll ans = 0;
const ll mod = 1e9 + 7;
ll a, b;
void init(){
    ans = 0;
    a = 0, b = 0;
    scanf("%d", &n);
    fors(i,1,n+1) {
        scanf("%d", &p[i]);
        if(p[i] == 2) x[i] = 1, y[i] = 0, a++;
        else x[i] = 0, y[i] = 1, b++;
        fa[i] = i;
    }
    ans = a*(a-1)*(a-2)/6 + a*(a-1)/2 * b;
}
void sol(){
    printf("%lld\n", ans%mod);
    fors(i,1,n){
        int u,v; scanf("%d%d", &u, &v);
        u = fnd(u); v = fnd(v);
        ll res2 = a-x[u]-x[v];
        ll res1 = b-y[u]-y[v];
        ans -= res2*x[u]*x[v];
        ans -= res2*x[u]*y[v];
        ans -= res2*x[v]*y[u];
        ans -= x[u]*x[v]*res1;

        fa[u] = v;
        x[v] += x[u];
        y[v] += y[u];
        printf("%lld\n", ans%mod);
    }
}
int main()
{
    int T; cin>>T;
    while(T--){
        init();
        sol();
    }
}

1006 X Number

不知道大家用的是啥方法……这里我写了挺麻烦的代码
首先把询问拆分成两个[1,x]区间的询问,常规操作
然后当最高位的数字没有达到上限的时候,剩余的数字其实是可以随便填的
维护一个当前每个数字填了多少,cur[i]表示数字i填了cur[i]个
这样假设最高位是a[i],我们从1填到a[i]-1的时候,后面都可以随便填了。到次高位也是这样考虑,唯一不同的是cur数组里有了上一位的影响。
所以就是枚举前k-1位和上限一样,枚举第k位是什么这样做下来
那么随便填要让d是最多的,可以枚举d的个数,这样其他数字的个数就被限制了。问题可以转换为:
有n个位置要填,可以填9种数字(除了d以外的数字),第i个数字填的个数不能超过 a i a_i ai个,问有多少填的方案。(数字的个数相同而填的位置不同视为不同方案)
对第i个数字生成函数 ( 1 + x + 1 2 ! x 2 + 1 3 ! x 3 . . . 1 a i ! x a i ) (1+x+\frac{1}{2!}x^2+\frac{1}{3!}x^3...\frac{1}{a_i!}x^{a_i}) (1+x+2!1x2+3!1x3...ai!1xai)
一共生成9个多项式,把这些多项式乘起来,取 x n x^n xn的系数乘上n!就是这个问题的答案。
补充
dp的求法是,dp[i]][j]表示前i个数字填了j个位置,然后枚举当前数字填几个进行转移即可。
然后数位dp的过程中再考虑一下前导零的影响就好。
TLE之后稍微记忆化一下……
丑陋的代码↓
不取大质数的话可以取两个质数之后使用中国剩余定理还原答案(这样就可以不用快速乘了,速度大概能快十来倍吧,应该也就不用记忆化了——来自其他过了这题的队伍)

#include
#define ll long long
#define pb push_back
#define lowbit(x) ((x)&(-(x)))
#define mid ((l+r)>>1)
#define lson rt<<1, l, mid
#define rson rt<<1|1, mid+1, r
#define fors(i, a, b) for(int i = (a); i < (b); ++i)
using namespace std;
const ll mod = 100000000000000003LL;
ll mul(ll a, ll b){
    ll res = 0;
    while(b){
        if(b&1) res = (res + a)%mod;
        a = (a+a)%mod;
        b >>= 1;
    }
    return res;
}
ll qm(ll a, ll b){
    ll res = 1;
    while(b){
        if(b&1) res = mul(res, a);
        a = mul(a,a);
        b >>= 1;
    }return res;
}
ll fac[20], ifac[20];
int a[25], cnt;
int d;
int cur[10];
int lim[10];//每个数字的限制
ll A[20], B[20], C[20];
void mul(){
    for(int i = 0; i < 20; ++i){
        C[i] = 0;
        for(int j = 0; j <= i; ++j){
            int k = i-j;
            C[i] = (C[i] + mul(A[j], B[k]))%mod;
        }
    }
    for(int i = 0; i < 20; ++i){
        A[i] = C[i];
    }
}
ll Com(int n, int k){
    return mul(mul(fac[n], ifac[k]), ifac[n-k]);
}
ll get_ans(int len){//根据限制获取答案
    if(len == 0) return 1;
    fors(i,0,20) A[i] = 0;
    A[0] = 1;
    fors(i,0,10){
        if(i == d) continue;
        fors(j,0,20){
            if(j <= lim[i]) B[j] = ifac[j];
            else B[j] = 0;
        }
        mul();
    }
    ll ans = mul(fac[len], A[len]);
    return ans;
}
map<ll, ll> mp[18][19];
ll no_lim(int len){//len个位置随意填,保证d的数量独无二的多
    if(len == 0){
        fors(i,0,10) {
            //cout<<"i:"<
            if(i != d && cur[i] >= cur[d]) return 0;
        }
        return 1;
    }
    int cnt[19];
    fors(i,0, 19) cnt[i] = 0;
    fors(i, 0, 10) if(i!=d) cnt[cur[i]]++;
    ll state = 0;
    for(int i = 18; i >= 0; --i){
        state = state*10+cnt[i];
    }
    if(mp[len][cur[d]].find(state) != mp[len][cur[d]].end()) return mp[len][cur[d]][state];
    ll ans = 0;
    int sum = 0;
    fors(i,0,10) sum += cur[i];//填了的数字个数
    sum += len;//总长度
    int down = (sum+18)/10;//d数量的下限
    int up_half = sum/2;//需要考虑的d的上限
    //cout<<"down:"<
    for(int d_num = down; d_num <= up_half; ++d_num){
        if(d_num < cur[d]) continue;//不够
        if(d_num-cur[d] > len) break;//爆了
        int flag = 1;
        fors(i, 0, 10) {
            if(i == d) continue;
            if(cur[i] >= d_num){
                flag = 0; break;
            }
            lim[i] = d_num-1-cur[i];
        }
        if(!flag) continue;
        int need_d = d_num-cur[d];

        ans = (ans + mul(Com(len, need_d),get_ans(len-need_d)))%mod;
    }
    //cout<<"Ans:"<
    for(int i = up_half+1; i <= sum; ++i){
        if(i < cur[d]) continue;//
        int need_d = i-cur[d];
        if(len < need_d) break;//放不下
        //cout<<"len:"<
        ll t = Com(len, need_d);
        //cout<<"i:"<
        fors(j,0,len-need_d) t = t*9%mod;
        ans = (ans + t)%mod;
    }
    mp[len][cur[d]][state] = ans;
    return ans;
}
ll sol(ll x){
    if(x < 10) return x >= d;
    cnt = 0;
    while(x){
        a[++cnt] = x%10; x /= 10;
    }
    ll ans = 0;
    for(int i = cnt; i > 0; --i){
        int down = 1; if(i < cnt) down = 0;
        for(int j = down; j < a[i]; ++j){
            cur[j]++;
            //cout<<"i:"<
            ans = (ans + no_lim(i-1))%mod;
            cur[j]--;
        }
        cur[a[i]]++;
        //cout<<"i:"<
    }
    //fors(i,0,10) cout<<"i:"<
    //cout<<"!"<
    ans = (ans + no_lim(0))%mod;
    //cout<<"!"<
    //cout<<"ans:"<
    memset(cur, 0, sizeof cur);
    for(int i = cnt-1; i > 0; --i){
        for(int j = 1; j < 10; ++j){
            cur[j]++;
            ans = (ans + no_lim(i-1))%mod;
            cur[j]--;
        }
    }
    if(d == 0) ans++;
    return ans;
}
int main()
{
    ifac[0] = fac[0] = 1;
    fors(i,1,20) fac[i] = mul(fac[i-1], i), ifac[i] = qm(fac[i], mod-2);
    int T; cin>>T;
    while(T--){
        ll l ,r;
        scanf("%lld%lld%d", &l, &r, &d);
        //cout<<"r:"<
        printf("%lld\n", sol(r)-sol(l-1));
    }
}

1009 Parentheses Matching

从左到右,用栈维护左括号的位置,另一个数组维护还没使用的星号,当遇到左括号就压入栈,遇到星号就放进数组,遇到右括号先看能不能消掉左括号,不能就看看能不能和最小的星位置结合,如果再不行就无解。
结束之后如果栈里面还有左括号,那就用最大位置的星号去和它结合。直到左括号没了或者星号没了。

#include
#define ll long long
#define pb push_back
#define lowbit(x) ((x)&(-(x)))
#define mid ((l+r)>>1)
#define lson rt<<1, l, mid
#define rson rt<<1|1, mid+1, r
#define fors(i, a, b) for(int i = (a); i < (b); ++i)
using namespace std;
const int maxn = 1e5 + 50;
char s[maxn];
int star[maxn*5], head, tail;
int st[maxn], tp;
void init(){
    scanf("%s", s);
    tp = head = tail = 0;
}
int ans[maxn];
void sol(){
    int n = strlen(s);
    fors(i,0,n) ans[i] = 0;
    int ok = 1;
    fors(i,0,n){
        if(s[i] == '*') star[tail++] = i;
        else if(s[i] == '(') st[tp++] = i;
        else if(s[i] == ')'){
            if(tp > 0) tp--;
            else if(head < tail){
                ans[star[head]] = -1;//标记为左括号
                head++;
            }else{
                ok = 0; break;
            }
        }else assert(0);
    }
    if(!ok){
        printf("No solution!\n"); return;
    }
    while(tp > 0){
        if(tail > head && star[tail-1] > st[tp-1]){
            tp--;
            ans[star[tail-1]] = 1;//标记为右括号
            tail--;
        }else{
            ok = 0; break;
        }
    }
    if(!ok){
        printf("No solution!\n"); return;
    }
    fors(i,0,n){
        if(s[i] != '*') printf("%c", s[i]);
        else if(ans[i] == -1) printf("(");
        else if(ans[i] == 1) printf(")");
    }printf("\n");
}
int main()
{
    int T; cin>>T;
    while(T--){
        init(); sol();
    }
}

你可能感兴趣的:(训练补题)