2016弱校聯萌十一專場10.5


A.

因為字符串不可以交叉,其實容易解

把不同字符的可用區域看成一個區間,用類似於鏈表的方法連接起來

查詢的就是查詢到的鏈表數量/4(當然右區間必須屬於y)

區間查詢用倍增或線段樹都可以

//倍增
#include <cstdio>
#include <cstring>

char s[100005];
int m, n, pe, pa, ps, py, dep;
int pre[100005], ed[100005];
int fa[100005][20];

int main() {
    scanf("%s", s);
    scanf("%d", &m);
    n = strlen(s);
    for (int i = 0; i < n; ++i) {
        if (s[i] == 'e') {
            pre[i + 1] = py;
            pe = i + 1;
        }
        if (s[i] == 'a') {
            pre[i + 1] = pe;
            pa = i + 1;
        }
        if (s[i] == 's') {
            pre[i + 1] = pa;
            ps = i + 1;
        }
        if (s[i] == 'y') {
            pre[i + 1] = ps;
            py = i + 1;
        }
        ed[i + 1] = py;
    }
    for (int i = 1; i <= n; ++i) fa[i][0] = pre[i];
    for (dep = 1; 1 << dep < n; ++dep);
    for (int i = 1; i < dep; ++i)
        for (int j = 1; j <= n; ++j)
            fa[j][i] = fa[fa[j][i - 1]][i - 1];
    for (; m--;) {
        int l, r;
        scanf("%d%d", &l, &r);
        int x = ed[r];
        int res = 1;
        for (int i = dep - 1; i >= 0; --i) {
            if (fa[x][i] >= l) {
                x = fa[x][i];
                res += 1 << i;
            }
        }
        printf("%d\n", res / 4);
    }
    return 0;
}
View Code

 

B.

給一堆點,要求連最小生成樹,注:邊的權值是兩邊點的權值異或和。

對整個點集合的權值建0/1 trie樹

可以把一個結點當成對應的點集合

如果此點只有1子樹則此結點集合就是下面一層那點的

否則此點對應連接左右兩子樹對應集合的最小權值邊

trie樹碰到有左右子樹的,對1個子樹暴力查詢另1子樹的元素,得到最小異或和權值o(nlogm)

如果沒有想到把2個子樹以o(n)的時間合並的方法就老實1個1個合並先

再說可能時間也不夠

但是真的沒想到有人真的就把2個子樹以o(n)的時間合並了

整個合並也不是不可以,不過這樣就要搞vector儲存每一層的結點可能性(跟我那時想到一塊去了。。。只不過我怕復雜度太高不敢下碼)

並且如果能找到異或位0的位置就必須置一樣

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int n, tot;
ll ans;
int a[4000009][2];
int num[4000009];
int dp[400009], dd[400009];
int dp1[400009], dd1[400009];
void add(int x)
{
    int q = 0;
    ++num[0];
    for(int i = 30; ~i; --i)
    {
        bool w = x >> i & 1;

        if(!a[q][w])
        {
            a[q][w] = ++tot;
        }
        ++num[q = a[q][w]];
    }
}
void zz(int x, int t)
{
    if(t == -1)return ;
    int l = a[x][0], r = a[x][1];
    if(l && num[l] > 1)zz(l, t - 1);
    if(r && num[r] > 1)zz(r, t - 1);
    if(!l || !r)return ;
    if(num[l] && num[r])
    {
        int q = 1 << t, *q1 = dp, *w1 = dd, *q2 = dp1, *w2 = dd1, num1 = 1, num2;
        q1[0] = l;
        w1[0] = r;
        for(int i = t - 1; ~i; --i)
        {
            bool f = 1;
            for(int j = 0; j < num1; ++j)
            {
                if((a[q1[j]][0]&&a[w1[j]][0]&&num[a[q1[j]][0]] && num[a[w1[j]][0]]) 
                || (a[q1[j]][1]&&a[w1[j]][1]&&num[a[q1[j]][1]] && num[a[w1[j]][1]]))
                {
                    f = 0;
                    break;
                }
            }
            num2 = 0;
            for(int j = 0; j < num1; ++j)
            {
                if(a[q1[j]][0]&&a[w1[j]][f]&&num[a[q1[j]][0]] && num[a[w1[j]][f]])
                {
                    q2[num2] = a[q1[j]][0];
                    w2[num2++] = a[w1[j]][f];
                }
                if(a[q1[j]][1]&&a[w1[j]][1 ^ f]&&num[a[q1[j]][1]] && num[a[w1[j]][1 ^ f]])
                {
                    q2[num2] = a[q1[j]][1];
                    w2[num2++] = a[w1[j]][1 ^ f];
                }
            }
            swap(q1, q2);
            swap(w1, w2);
            swap(num1, num2);
            if(f)q |= 1 << i;
        }
        ans += q;
    }
}
int main()
{
    ans=0;
    scanf("%d", &n);
    for(int i = 0; i < n; ++i)
    {
        int p;
        scanf("%d", &p);
        add(p);
    }
    zz(0, 30);
    printf("%lld\n", ans);
}
View Code

 

C.

給m個空環,其中第i個長度為si,把1~sum(s)這些數填上去,要求相鄰數互質,並輸出具體排布,填不下去則輸出"QQ"

還好題目的復雜度較低。。。

不能做到偶數之間至少隔1格,環就不能填,否則肯定能填上環(別問我怎么知道的,我也是靠直覺,n~n+1個奇數肯定能對上n個互質的偶數)

先看m個環能否容下總的偶數個數,不能就是無解。

然后這題是special judge,可以隨便組。

我的方法:先把1~sum(s)這些數鏈按照題目給的環長切成對應環(gcd(n,n+1)==1,所以相鄰數肯定互質)

然后看那幾個環頭尾是否互質,不是則遍歷左邊的環,找出交換數后兩個環繼續滿足互質鏈的位置,然后交換。

查找次數可達2000*500*6=6000000,實際第2個環可能交換不了那么多次,第3、4也不用整個環都遍歷一次

 

D.

給多個點,對任意2個點連邊(不與點或線相交),誰先畫不了誰輸,問誰能在最佳策略下贏

其實不管怎么連都是三角剖分,所以不存在什么需要博弈論的東西

求三角剖分后總的邊數是否偶數即可

發現一個凸包多加點使其仍為凸包,相當於向外多加2條邊;在凸包內部加點,相當於加3條邊

然后規律就出來了

 

E.

一個n*m棋盤,問能走遍棋盤的騎士棋種類數(只有一次向xy兩軸走至少1格並不走出棋盤的騎士)

可以知道不能走所有格的騎士有這些情況:

設dx<=dy,n<=m

gcd(dx,dy)!=1 騎士只能走指定的格集合

(dx+dy)%2==0 那些(x+y)%2==1的點走不到

dy>m/2 騎士不能走2個方向,使得一些點走不到

dx+dy>n 中間區域無法到達

老規矩,用莫比烏斯容斥

由於還有奇偶判定,直接算還是有些麻煩

祭出公式:1<=k<=min(m,n) miu(k)*f(floor(m/k),floor(n.k))

其中f(m,n)就是不包括dx dy不互質,除去上述無法走全棋盤的騎士種類數

由於處於gcd(dx,dy)%2==0的格子肯定會被除去,上述公式的k是不算偶數的

f(m,n) o(1)求出,答案因為floor()可以歸並某些答案的關系,可以o(sqrt(min(m,n)))

 

F.

求f(f(n))%20160519,其中f(0)=0 f(1)=1 f(i)=f(i-1)+f(i-2)

fib數列其實有循環節的,當運行到f(k-1)%k==k-1 f(k)==1 那循環節就是k

順帶補下這種數列的循環節知識:

當模數是質數模5余1或4,則這種數是5的二次剩余,則循環節是p-1的因子

當模數是質數模5余2或3,則這種數是5的二次非剩余,則循環節是2p+2的因子

當模數是合數,p=p1^a1*p2^a2*.....

可以想到積性函數,循環節是lcm(p1^(a1-1)*(p1的對應循環節數)*p2^(a2-1)*(p2的對應循環節數)*...)

lcm(...)是里面所有數的最小公倍數

然后快速冪一波AC

 

H.

題意:只有'A'-'Z'的字符串*s對應hash函數是這樣得到的:h=0;for(i=0;i<n;i++)h=((h*p)+s[i])%m,給n,m,p問長度為n的字符串會哈希沖突的對數

用數位dp的思想可以知道,設dp(n,h)是長度n且哈希值為h的字符串個數,則dp(n,h)=for(i=0;i<m;i++)d(n1,i)*d(n-n1,(h-i*p^n1)mod m)

把第1維相同的值組成一個向量,可以發現其實就是多項式乘法

但是不可能真的就n次遞推,那樣o(nm*logn)超時

但是我們會發現,這個式子可以當成向量卷積

所以拆分dp的方法可以像快速冪一樣,儲存第1維是2^n的向量就可以,然后結果向量看當前n掃描到的2進制位是1是0決定乘不乘

多項式乘法可以FFT(喂精度被你吃了?)NTT(用CRT合並2個小素數模下結果就可以,或者直接long long快速乘)=>o(nlogmlogn)

 

I.

題意:求出[l,r]所有單調不遞增||單調不遞減數

數位dp,沒得說的,注意常數太大,o(10logn)會超時

所以就愉快地記憶化搜索,把指定位置是包括0~9位的全部填上

或者直接開場直接處理9、99、999...這些數的對應計數

這樣就可以o(logn)胡搞了

果然不應該搞這么久的。。。比賽哪有那么多時間給你搞

以上是標准做法,已AC,我的非主流做法在下:

我是想用前綴和把搜索過程的復雜度壓縮一下的,結果WA

大神求教

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<queue>
#include<stack>
#include<math.h>
#include<vector>
#include<map>
#include<set>
#include<stdlib.h>
#include<cmath>
#include<string>
#include<algorithm>
#include<iostream>
#include<assert.h>
using namespace std;
typedef long long ll;
int max(int a,int b){return a>b?a:b;}
int min(int a,int b){return a<b?a:b;}

ll fac[21];
ll tal[20][10][2],pre[20][10][2];
//0:down,1:all
ll f(int n){return n<0?0:fac[n];}
void gettal()
{
    fac[0]=1;
    memset(tal,0,sizeof(tal));
    memset(pre,0,sizeof(pre));
    int i,j;
    for(i=1;i<21;i++)fac[i]=fac[i-1]*i;
    for(i=0;i<10;i++)tal[0][i][1]=1,tal[0][i][0]=0,pre[0][i][1]=pre[0][i][0]=i+1;
    for(i=1;i<20;i++)
    {
        for(j=0;j<10;j++)
        {
            tal[i][j][0]=j==0?0:f(i+j-1)/f(j-1)/f(i);
            tal[i][j][0]+=tal[i-1][j][0];

            tal[i][j][1]=tal[i][j][0]+tal[i-1][j][1]-tal[i-1][j][0];
            tal[i][j][1]+=j==9?0:f(8-j+i)/f(8-j)/f(i);

            pre[i][j][0]=j?pre[i][j-1][0]+tal[i][j][0]:tal[i][j][0];
            pre[i][j][1]=j?pre[i][j-1][1]+tal[i][j][1]:tal[i][j][1];
        }
    }
}
ll solve(ll n)
{
    if(!n)return 1;
    int i,j,path,ph;
    ll tmp,ans=0;
    for(i=0,tmp=1;n>=tmp;i++,tmp*=10);i--;tmp/=10;
    int stat=1;
    path=n/tmp;
    ans+=pre[i][path-1][1]-pre[i][0][1];
    tmp/=10;
    for(i--;i>0;i--)
    {
        ans+=pre[i][9][1]-pre[i][0][1];
        ph=(n%(tmp*10))/tmp;
        if(stat==1)//keep
        {
            if(path>ph){stat=0;if(ph)ans+=pre[i][ph-1][0]+ph;}
            else if(path==ph){if(ph)ans+=pre[i][ph-1][0]+ph;}
            else
            {
                stat=2;
                ans+=pre[i][ph-1][0]+ph;
                ans+=tal[i][ph][1],
                ans+=pre[i][ph-1][1]-pre[i][path][1];
                ans-=pre[i][ph-1][0]-pre[i][path][0];
            }
            path=ph;
        }
        else if(stat==2)//increase
        {
            if(path>ph)
            {
                stat=3;
                break;
            }
            ans+=pre[i][ph-1][1]-pre[i][path-1][1]-pre[i][ph-1][0]+pre[i][path-1][0];
            path=ph;
        }
        else if(stat==0 && path)//decrease
        {
            if(ph)ans+=pre[i][min(path,ph)-1][0]+min(path,ph);
            path=min(path,ph);
        }
        tmp/=10;
    }
    ans+=pre[0][9][1];
    ph=(n%(tmp*10))/tmp;
    if(stat==0)ans+=min(path,ph)+1;
    else if(stat==1)ans+=ph+1;
    else if(stat==2)ans+=max(0,ph-path+1);
    return ans;
}
int main()
{
    int t;
    ll l,r;
    gettal();
    scanf("%d",&t);
    while(t--)
    {
        scanf("%lld%lld",&l,&r);
        printf("%lld\n",solve(r)-solve(l-1));
    }
    return 0;
}
View Code

 

J.

題意。。。實在難以描述,看https://acm.bnu.edu.cn/v3/statments/taiwan2016.pdf吧

這題一看像是多項式乘法,但是下面的不是加而是取其最大,所以什么fft,nft都可以歇歇了

其實也不難想到解法,把最大數填上對應位置,之后從大到小填,碰到已填元素就跳過

直到整個表都填滿為止

為什么這樣看似o(n^2)的解法不會超時。。。我也不知道。。。

寫時SB了,數組開成10^5結果老RE

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<queue>
#include<stack>
#include<math.h>
#include<vector>
#include<map>
#include<set>
#include<stdlib.h>
#include<cmath>
#include<string>
#include<algorithm>
#include<iostream>
#include<assert.h>
using namespace std;
typedef long long ll;
int max(int a,int b){return a>b?a:b;}
int min(int a,int b){return a<b?a:b;}
int a[2][200060];
int occupy[200060];
int main()
{
    int n,j,i,t;
    memset(occupy,-1,sizeof(occupy));
    scanf("%d",&n);
    for(j=0;j<2;j++)
    {
        for(i=0;i<n;i++)
        {
            scanf("%d",&t);
            a[j][t]=i;
        }
    }
    int cnt=n;
    for(i=n*2-2;cnt;i--)
    {
        for(j=max(0,i-n+1);j<n && cnt;j++)
        {
            if(i<j)continue;
            if(i-j>=n)break;
            t=a[0][j]+a[1][i-j];
            if(occupy[t%n]==-1)occupy[t%n]=i,cnt--;
        }
    }
    for(i=0;i<n;i++)printf("%d%c",occupy[i],i==n-1?'\n':' ');
    return 0;
}
View Code

 


注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2020 ITdaan.com