Moscow Pre-Finals Workshop 2020 - Legilimens+Coffee Chicken Contest 部分题解

A - Everyone Loves Playing Games

首先将X\oplus a[x_1] \oplus a[x_2] \oplus ... \oplus a[x_n]\oplus b[x_1] \oplus b[x_2] \oplus ... \oplus b[x_m],然后将a,bx删除,y修改为x\oplus y

问题转化为了选择或者不选择x\oplus y,构造线性基A,B

假如对于A,B的第i位都存在,设A的第i位线性基为t_1B的第i位线性基为t_2。那么需要将X修改为X \oplus t_1,将t_1\oplus t_2插入线性基A

最后的X即为答案,正确的原因是将t_1\oplus t_2插入线性基A等价于是反悔贪心。

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int N = 1e4 + 5;
int n, m;
struct LB
{
    ll b[65];
    void clear() { memset(b, 0, sizeof(b)); }
    void ins(ll x)
    {
        for(int i=62; i>=0; i--)
            if((x>>i)&1)
            {
                if(!b[i]) 
                {
                    b[i] = x;
                    return;
                }
                x ^= b[i];
            }
    }
}A, B;
void solve()
{
    scanf("%d%d", &n, &m);
    ll X = 0;
    A.clear(), B.clear();
    for(int i=1; i<=n; i++)
    {
        ll x, y; scanf("%lld%lld", &x, &y);
        X ^= x;
        A.ins(x^y);
    }
    for(int i=1; i<=m; i++)
    {
        ll x, y; scanf("%lld%lld", &x, &y);
        X ^= x;
        B.ins(x^y);
    }
    ll ans = X;
    for(int i=62; i>=0; i--)
    {
        if(!A.b[i] && !B.b[i]) continue;
        if(!A.b[i]) ans = min(ans, ans^B.b[i]);
        else if(!B.b[i]) ans = max(ans, ans^A.b[i]); 
        else
        {
            if((ans>>i)&1) ans ^= A.b[i]; 
            A.ins(A.b[i]^B.b[i]);
        }
    }
    printf("%lld\n", ans);
}
int main()
{
    int _; scanf("%d", &_);
    while(_--) solve();
    return 0;
}

 

B - Gifted Composer

首先发现一个事情,长度\dpi{150} x的字符串在时间\dpi{150} x的时刻是循环节的长度,并在接下来连续一段时间内都是循环节的长度,在某一时刻开始不是循环节的长度,直至结束都不再是循环节的长度。

这样就可以枚举循环节的长度并二分循环节的结束时间。找到结束时间后,在时间轴上差分即可。

#include<bits/stdc++.h>
using namespace std ;
typedef long long ll ;
const int maxn = 1e6 + 10 ;
template<unsigned mod , unsigned base>
struct Hash
{
    unsigned p[maxn] , pre[maxn] ;
    Hash() 
    {
        p[0] = 1 ;
        for(int i = 1 ; i < maxn ; i ++)  p[i] = 1ull * p[i - 1] * base % mod ;
        pre[0] = 0 ;
    }
    void build(const char *s)  //s[0 .. len - 1] <-> pre[1 .. len]
    {
        int len = strlen(s) ;
        for(int i = 0 ; i < len ; i ++)  pre[i + 1] = (1ull * pre[i] * base + s[i]) % mod ;
    }
    unsigned operator()(int l , int r)  // pre[l + 1 ... r] <-> s[l , r - 1]
    {
        return (pre[r] - 1ull * pre[l] * p[r - l] % mod + mod) % mod ;
    }
} ;
struct d_hash // double hash
{
    Hash<997137961 , 753> h1 ;
    Hash<1003911991 , 467> h2 ;
    void build(const char *s) 
    {
        h1.build(s) ;
        h2.build(s) ;
    }
    uint64_t operator() (int l , int r)  // pre[l + 1 ... r]
    {
        return uint64_t(h1(l , r)) << 32 | h2(l , r) ;
    }
} hasher ;
int main()
{
    std::ios::sync_with_stdio(false) , cin.tie(0) ;
    int n ;
    cin >> n ;
    list<char> s ;
    int l = 0 , r = 0 ;
    vector<pair<int , int>> pos ;
    for(int i = 1 ; i <= n ; i ++)
    {
        string s1 , s2 ;
        cin >> s1 >> s2 ;
        char c ;
        //因为是判循环节种类数,所以循环节长度不重要,缩减至一个字符有助于卡常
        if(s2 == "sol")  c = 'z' ; //重复首字符
        else  c = s2[0] ;
        if(s1 == "p")
        {
            s.push_front(c) ;
            l ++ ;
        }
        else
        {
            s.push_back(c) ;
            r ++ ;
        }
        pos.push_back({l , r}) ;
    }
    for(auto &u : pos)  u.first = l - u.first , u.second = l + u.second - 1 ;
    hasher.build(string(s.begin() , s.end()).c_str()) ;
    vector<int> ans(n + 10 , 0) ;
    auto ok = [&](int k , int mid)
    {
        int l = pos[mid - 1].first ;
        int r = pos[mid - 1].second ;
        return hasher(l , r - k + 1) == hasher(l + k , r + 1) ;
    } ;
    auto cal = [&](int l , int r)  //二分查找结束时间
    {
        int res = l ;
        int k = l ;
        while(l <= r)
        {
            int mid = (l + r) / 2 ;
            if(ok(k , mid))  res = mid , l = mid + 1 ;
            else  r = mid - 1 ;
        }
        return res ;
    } ;
    for(int i = 1 ; i <= n ; i ++)
    {
        //在时间轴上差分
        ans[i] ++ ; 
        int t = cal(i , n) ;
        ans[t + 1] -- ;
    }
    for(int i = 2 ; i <= n ; i ++)  ans[i] += ans[i - 1] ;
    for(int i = 1 ; i <= n ; i ++)  cout << ans[i] << '\n' ;
    return 0 ;
}

 

C - An Unsure Catch

留坑。

 

D - String Theory

#include<bits/stdc++.h>
using namespace std ;
const int maxn = 3e5 + 10 ;
struct Sa //如果是多个测例,记得清空 s
{
    int rk[maxn << 1] , sa[maxn << 1] , height[maxn << 1] ;
    int tmp[maxn << 1] , cnt[maxn] ;
    char s[maxn] ;
    int st[maxn][25] ;
    void cal(int n , int m)
    {
       n ++ ;
       for(int i = 0 ; i < n * 2 + 5 ; i ++)
         rk[i] = sa[i] = height[i] = tmp[i] = 0 ;//开2 倍空间
       for(int i = 0 ; i < m ; i ++)  cnt[i] = 0 ;
       for(int i = 0 ; i < n ; i ++)  cnt[rk[i] = s[i]] ++ ;
       for(int i = 1 ; i < m ; i ++)  cnt[i] += cnt[i - 1] ;
       for(int i = 0 ; i < n ; i ++)  sa[-- cnt[rk[i]]] = i ;
       for(int k = 1 ; k <= n ; k <<= 1)
       {
         int j = 0 ;
         for(int i = 0 ; i < n ; i ++)
         {
           j = sa[i] - k ;
           if(j < 0)  j += n ;
           tmp[cnt[rk[j]] ++] = j ;
         }
         sa[tmp[cnt[0] = 0]] = j = 0 ;
         for(int i = 1 ; i < n ; i ++)
         {
           if(rk[tmp[i]] != rk[tmp[i - 1]]
           || rk[tmp[i] + k] != rk[tmp[i - 1] + k])
             cnt[++ j] = i ;
           sa[tmp[i]] = j ;
         }
         memcpy(rk , sa , n * sizeof(int)) ;
         memcpy(sa , tmp , n * sizeof(int)) ;
         if(j >= n - 1)  break ;
       }
       height[0] = 0 ;
       for(int i = 0 , k = 0 , j = rk[0] ; i < n - 1 ; i ++ , k ++)
         while(~k && s[i] != s[sa[j - 1] + k])
           height[j] = k -- , j = rk[sa[j] + 1] ;
    }   
    void build_lcp(int n)
    {
        for(int i = 1 ; i <= n ; i ++)  st[i][0] = height[i] ;
        for(int j = 1 ; j <= 20 ; j ++)
            for(int i = 1 ; i + (1 << j) - 1 <= n ; i ++)
                st[i][j] = min(st[i][j - 1] , st[i + (1 << (j - 1))][j - 1]) ;
    }
    int lcp(int l , int r)
    {
        if(l > r)  swap(l , r) ;
        l ++ ;
        int len = log2(r - l + 1) ;
        return min(st[l][len] , st[r - (1 << len) + 1][len]) ;
    }
} sa[2] ;
int main()
{
    std::ios::sync_with_stdio(false) , cin.tie(0) ;
    int T ;
    cin >> T ;
    while(T --)
    {
        int k ;
        cin >> k ;
        cin >> sa[0].s ;
        int len = strlen(sa[0].s) ;
        if(k == 1)
        {
            cout << 1ll * len * (len + 1) / 2 << '\n' ;
            continue ;
        }
        for(int i = 0 ; i < len ; i ++)  sa[1].s[i] = sa[0].s[len - 1 - i] ;
        sa[0].cal(len , 200) ;
        sa[0].build_lcp(len) ;
        sa[1].cal(len , 200) ;
        sa[1].build_lcp(len) ;
        auto rev = [&](int x)
        {
            return len - 1 - x ;
        } ;
        long long ans = 0 ;
        for(int i = 1 ; i <= len / k ; i ++)
        {
            for(int j = 0 ; j < len ; j += i)
            {
                int p = j ;
                while(p + i < len && sa[0].lcp(sa[0].rk[p] , sa[0].rk[p + i]) >= i)  p += i ;
                if(p + i - 1 <= len - 1 && (p == j || sa[0].lcp(sa[0].rk[j] , sa[0].rk[p]) >= i))  p += i ;
                if((p - j) / i >= k - 1)
                {
                    int x = p ;
                    int lcp1 = 0 ;
                    if(x < len)  lcp1 = sa[0].lcp(sa[0].rk[j] , sa[0].rk[x]) ;
                    int y = j - 1 ;
                    int lcp2 = 0 ;
                    if(y >= 0)  lcp2 = sa[1].lcp(sa[1].rk[rev(j - 1)] , sa[1].rk[rev(j + i - 1)]) ;
                    ans += max(0 , lcp1 + lcp2 + p - j - i * k + 1) ;
                }
                if(p > j)  j = p - i ;                
            }
        }
        cout << ans << '\n' ;
    }
    return 0 ;
}

 

E - Road Construction

留坑。

 

F - Girlfriend

留坑。

 

G - Blackjack

定义\dpi{150} dp[i][j][k]表示前\dpi{150} i张牌选择了\dpi{150} j张牌且权值和是\dpi{150} k的概率。转移方程是\dpi{150} dp[i][j][k] = dp[i - 1][j - 1][k - x[i]] * \frac{j}{n - j + 1}

题意要求在选择\dpi{150} j张牌后权值和\dpi{150} w \in (a,b]且在选择\dpi{150} j - 1牌后权值和\dpi{150} w \notin (a,b]

因此需要枚举最后一张选择的牌是什么,即第\dpi{150} j张牌是什么。这样复杂度是\dpi{150} O(n^4)

这个背包是加法的,所以可以枚举撤销最后一张选择的牌的贡献,计算后可以是可以复原最后一张选择的牌的贡献的。这样复杂度是\dpi{150} O(n^3)

#include<bits/stdc++.h>
using namespace std ;
typedef double db ;
const int maxn = 500 + 10 ;
db dp[2][maxn][maxn] ; //dp[i][j][k]表示前i个数选择j张且权值和是k的概率
db tmp[maxn][maxn] ;
int main()
{
    std::ios::sync_with_stdio(false) , cin.tie(0) ;
    int n , a , b ;
    cin >> n >> a >> b ;
    vector<int> x(n + 1 , 0) ;
    for(int i = 1 ; i <= n ; i ++)  cin >> x[i] ;
    dp[0][0][0] = 1.0 ;
    int op = 0 ;
    for(int i = 1 ; i <= n ; i ++)
    {
        op = 1 - op ;
        for(int j = 0 ; j <= i ; j ++)
            for(int k = 0 ; k <= 500 ; k ++)
            {
                dp[op][j][k] = dp[1 - op][j][k] ;
                if(j >= 1 && k >= x[i])  dp[op][j][k] += dp[1 - op][j - 1][k - x[i]] * j / (n + 1 - j) ;
            }
    }
    db ans = 0.0 ;
    for(int i = 1 ; i <= n ; i ++)
    {
        //撤销
        for(int j = 0 ; j < n ; j ++)
            for(int k = 0 ; k <= 500 ; k ++)
            {
                tmp[j][k] = dp[op][j][k] ;
                if(j >= 1 && k >= x[i])
                    tmp[j][k] -= tmp[j - 1][k - x[i]] * j / (n + 1 - j) ;
            }

        //计算
        for(int j = 0 ; j < n ; j ++)
            for(int k = 0 ; k <= 500 ; k ++)
            {
                if(a - x[i] < k && k <= min(b - x[i] , a))
                    ans += tmp[j][k] / (n - j) ;
            }
    }
    cout << fixed << setprecision(12) << ans << '\n' ;
    return 0 ;
}

 

H - Yet Another Geometry Problem

留坑。

 

I - A Math Problem

留坑。

 

J - Gaokao

温暖的签到题。

#include<bits/stdc++.h>
using namespace std ;
int main()
{
    std::ios::sync_with_stdio(false) , cin.tie(0) ;
    int T ;
    cin >> T ;
    while(T --)
    {
        long long n ;
        cin >> n ;
        cout << (1ll << __builtin_popcountll(n - 1)) << '\n' ;
    }
    return 0 ;
}

 

K - Data Structure

留坑。

 

L - Landlord

离散化后dfs判连通块个数。

#include <bits/stdc++.h>
using namespace std;
const int N = 25;
struct Rec
{
    int x1, y1, x2, y2;
    void input() { scanf("%d%d%d%d", &x1, &y1, &x2, &y2); }
}p[2];
bool tag[N][N];
int upx, upy;
const int dir[4][2] = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
void dfs(int x, int y)
{
    tag[x][y] = 1;
    for(int i=0; i<4; i++)
    {
        int tx = x + dir[i][0], ty = y + dir[i][1];
        if(tx>=0&&tx<=upx&&ty>=0&&ty<=upy&&!tag[tx][ty]) dfs(tx, ty);
    }
}
void solve()
{
    p[0].input(); p[1].input();
    vector<int> x, y;
    for(int i=0; i<2; i++) x.push_back(p[i].x1<<1), x.push_back(p[i].x2<<1), x.push_back(p[i].x1<<1|1), x.push_back(p[i].x2<<1|1);
    for(int i=0; i<2; i++) y.push_back(p[i].y1<<1), y.push_back(p[i].y2<<1), y.push_back(p[i].y1<<1|1), y.push_back(p[i].y2<<1|1);
    sort(begin(x), end(x)); sort(begin(y), end(y));
    x.erase(unique(begin(x), end(x)), end(x));
    y.erase(unique(begin(y), end(y)), end(y));
    memset(tag, 0, sizeof(tag));
    for(int i=0; i<2; i++)
    {
        p[i].x1 = lower_bound(begin(x), end(x), p[i].x1<<1) - begin(x) + 1;
        p[i].y1 = lower_bound(begin(y), end(y), p[i].y1<<1) - begin(y) + 1;
        p[i].x2 = lower_bound(begin(x), end(x), p[i].x2<<1) - begin(x) + 1;
        p[i].y2 = lower_bound(begin(y), end(y), p[i].y2<<1) - begin(y) + 1;
        for(int a=p[i].x1; a<=p[i].x2; a++) tag[a][p[i].y1] = tag[a][p[i].y2] = 1;
        for(int b=p[i].y1; b<=p[i].y2; b++) tag[p[i].x1][b] = tag[p[i].x2][b] = 1;
    }
    upx = x.size() + 1, upy = y.size() + 1;
    // for(int i=0; i<=upx; i++)
    //     for(int j=0; j<=upy; j++) cout << tag[i][j] << " \n"[j==upy];
    int ans = 0;
    for(int i=0; i<=upx; i++)
        for(int j=0; j<=upy; j++) 
            if(!tag[i][j]) ++ans, dfs(i, j);
    printf("%d\n", ans);
}
int main()
{
    int _; scanf("%d", &_);
    while(_--) solve();
    return 0;
}

 

M - Travel Dream

留坑。

 

 

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值