POI 2015 題解

POI 2015 題解

在宋爺的教育下……我來自我教育一下,做一下近年的POI題……

論弱逼的自我修養

4385: [POI2015]Wilcze doły

Description

給定一個長度爲n的序列,你有一次機會選中一段連續的長度不超過d的區間,將裏面所有數字全部修改爲0。
請找到最長的一段連續區間,使得該區間內所有數字之和不超過p。

Solution

顯然,選擇 d 個一定比不足 d 個更優,然後我們就可以先維護一段區間,然後維護連續 d 的最大值,在所有的合法區間決策一下就行了。我們令 f(i)=sum[i+d1]sum[i1] ,發現如果 i<jf(i)<f(j) ,那 j 代表的一段永遠都不能成爲答案了,也就是決策的單調性,然後就可以單調隊列水過了

#include <stdio.h>
#include <string.h>
#include <iostream>
#include <algorithm>
const int N = 2000000+5;
using namespace std;
typedef long long LL;

LL q[N];
int l,r;
LL sum[N];
LL f[N],a[N];

inline LL read()
{
    LL x = 0,f=1;char ch = getchar();
    while(ch < '0' || ch > '9'){if(ch == '-')f=-1;ch = getchar();}
    while(ch >='0' && ch <='9'){x=(x<<1)+(x<<3)+ch-'0';ch = getchar();}
    return x*f;
}

int main()
{
//  freopen("ks.in","r",stdin);
//  freopen("ks.out","w",stdout);
    int n = read();
    LL p = read();
    int d = read();
    for(int i=1;i<=n;++i) a[i] = read(), sum[i] = a[i] + sum[i-1];
    for(int i=1;i<=n;++i) f[i] = sum[i+d-1] - sum[i-1];
    int ans = d;
    l = 1;
    r = 1;
    q[r] = 1;
    LL sum = f[1];
    for(int i=1,j=1;j<=n-d+1;)
    {
        while(sum - f[q[l]] <= p && j <= n-d+1){
            j ++;
            while(l <= r && f[q[r]] <= f[j] )--r;
            q[++r] = j; 
            sum += a[j+d-1];
        }
//      cout << i <<  " " << j << endl;
        ans = max(ans,j-i+d-1);
        while(sum - f[q[l]] > p && i<=j){
            while(l<=r && q[l]<=i) ++l;
            sum -= a[i++];
        }
    }
    cout << ans << endl;
}

4378[POI2015]Logistyka

Description

維護一個長度爲n的序列,一開始都是0,支持以下兩種操作:
1.U k a 將序列中第k個數修改爲a。
2.Z c s 在這個序列上,每次選出c個正數,並將它們都減去1,詢問能否進行s次操作。
每次詢問獨立,即每次詢問不會對序列進行修改。

Solution

我們先考慮,加入如果一段區間大於等於s 的數字的個數x 大於c,那顯然這些數字就可以了。其次如果一段區間小於s 的和大於等於s(cx) 顯然也是合法的,因爲此時小於s 的個數一定大於cx ,每次選擇能選的就行了,如果小於的話,顯然連總和都不夠,因爲這就是合法的判定了,至於個數和數字的和,直接離線離散加樹狀數組就行了。

#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <iostream>
#define lowbit(x) ((x)&(-x))
using namespace std;
const int N = 1000000;
typedef long long LL;

int T1[N+5];
LL T2[N+5];

struct ask
{
    int chose;
    int x,y;
}q[N+5];

void updata(int x,int num)
{
    while(x <= N )
    {
        T1[x] += (num >= 0) ? 1 : -1;
        T2[x] += (LL)num;
        x += lowbit(x);
    }
}

int ask1(int x)
{
    int ans = 0;
    while(x)
    {
        ans += T1[x];
        x -= lowbit(x);
    }
    return ans;
}

LL ask2(int x)
{
    LL ans = 0;
    while(x)
    {
        ans += T2[x];
        x -= lowbit(x);
    }
    return ans;
}

inline int read()
{
    int x=0,f=1;char ch = getchar();
    while(ch < '0' || ch > '9'){if(ch == '-')f=-1;ch = getchar();}
    while(ch >='0' && ch <='9'){x=(x<<1)+(x<<3)+ch-'0';ch = getchar();}
    return x*f;
}

int a[N+5];
int V[N<<1];
int tt;

int find(int x)
{
    int l = 1;
    int r = tt;
    while(l<=r)
    {
        int mid = (l+r)>>1;
        if(V[mid] == x) return mid;
        if(V[mid] < x) l = mid + 1;
        else r = mid - 1;
    }
    return 0;
}

int main()
{
    int n = read(), m = read();
    char str[10]; 
    for(int i=1;i<=m;++i)
    {
        scanf("%s",str);
        q[i].chose = (str[0] == 'U');
        q[i].x = read(),q[i].y = read();
        V[++tt] = q[i].y;
    }
    V[++tt] = 0;
    for(int i=1;i<=n;++i)
        updata(1,0);
    sort(V+1,V+tt+1);
    for(int i=1;i<=m;++i)
    {
        if(q[i].chose == 1)
        {
            int tt = find(a[q[i].x]);
            updata(tt,-a[q[i].x]);
            a[q[i].x] = q[i].y;
            tt = find(a[q[i].x]) ;
            updata(tt,a[q[i].x]);   
        }
        else
        {
            int tt = find(q[i].y);
            int tt1 = ask1(N) - ask1(tt-1);
            if(tt1 >= q[i].x){puts("TAK");continue;}
            LL tt2 = ask2(tt-1);
            if(tt2 >= ((LL)q[i].x - tt1)*q[i].y)
                puts("TAK");
            else puts("NIE");
        }
    }
}

3749: [POI2015]Łasuchy

圓桌上擺放着n份食物,圍成一圈,第i份食物所含熱量爲c[i]。

相鄰兩份食物之間坐着一個人,共有n個人。每個人有兩種選擇,吃自己左邊或者右邊的食物。如果兩個人選擇了同一份食物,這兩個人會平分這份食物,每人獲得一半的熱量。

假如某個人改變自己的選擇後(其他n-1個人的選擇不變),可以使自己獲得比原先更多的熱量,那麼這個人會不滿意。

請你給每個人指定應該吃哪一份食物,使得所有人都能夠滿意。

Solution

我們可以分析,對於一個人情況其實非常少,我們不妨嘗試第一個人的每一種情況,之後每一個人的合法情況就都能知道了,然後就是模擬了

這題作爲嘴巴選手AC還是非常容易的,但是真寫起來這是…………

#include <stdio.h>
#include <string.h>
#include <iostream>
#include <algorithm>
const int N = 1000000+5;
using namespace std;

int n;
int a[N];

int path[N][5],ans[N];

inline int read()
{
    int x=0,f=1;char ch = getchar();
    while(ch < '0' || ch > '9'){if(ch == '-')f=-1;ch = getchar();}
    while(ch >='0' && ch <='9'){x=(x<<1)+(x<<3)+ch-'0';ch = getchar();}
    return x*f;
}

bool work(int x)
{
    memset(path,0,sizeof path);
    path[1][x] = 1;
    for(int i=2;i<=n;++i)
    {
        if(path[i-1][1] && (a[i-1] <= (a[i] << 1))) path[i][1] = 1;
        if(path[i-1][4] && (a[i-1] <= a[i])) path[i][1] = 4;
        if(path[i-1][2] && (a[i-1]<<1) >= a[i]) path[i][2] = 2;
        if(path[i-1][3] && (a[i-1] >= a[i])) path[i][2] = 3;
        if(path[i-1][1] && (a[i-1] <= a[i])) path[i][3] = 1;
        if(path[i-1][4] && (a[i-1]<<1) <= a[i]) path[i][3] = 4;
        if(path[i-1][2] && (a[i-1]>=a[i])) path[i][4] = 2;
        if(path[i-1][3]&&(a[i-1]>=a[i]*2))path[i][4]=3;
    }
    if(path[n][x] == 0) return  0;
    for(int i=n;i>=1;--i)
    {
        if(x == 1) ans[i-1] = (i-1) %(n-1) + 1;
        if(x == 2) ans[i] = (i-1) %(n-1) + 1;
        if(x == 3) ans[i-1] = ans[i] = (i-1)%(n-1) + 1;
        x = path[i][x];
    }
    for(int i=1;i<n-1;++i) printf("%d ",ans[i]);
    return printf("%d\n",ans[n-1]), 1;
}

int main()
{
    n = read();
    for(int i=1;i<=n;++i) a[i] = read();
    a[++n] = a[1];
    for(int i=1;i<=4;++i) if(work(i)) return 0;
    puts("NIE");
    return 0;
}

4383: [POI2015]Pustynia

Description

給定一個長度爲n的正整數序列a,每個數都在1到10^9範圍內,告訴你其中s個數,並給出m條信息,每條信息包含三個數l,r,k以及接下來k個正整數,表示a[l],a[l+1],…,a[r-1],a[r]裏這k個數中的任意一個都比任意一個剩下的r-l+1-k個數大(嚴格大於,即沒有等號)。
請任意構造出一組滿足條件的方案,或者判斷無解。

Solution

首先,如果數字的個數比較少,我們可以這樣構建

1.如果存在一個數字 A 比另一個數字 BC , 那麼就從 AB 連一條邊權爲C 的邊.

2.對圖進行拓撲排序,把起點的值設爲 inf ,不斷限制中間的點的最大值

3.如果有正環或者有點的範圍不在規定的範圍之內就 GG

然而這題的點數太多了,耿直的建邊是n2 級別的,所以我們需要一些技♂巧

我們可以學習VfleakingA+BProblem 的經驗,利用線段樹來搞對於每一次的建邊,我們可以這樣

這是一顆正常的線段樹

![](~/getGraph (1).jpg)

我們 把它改造,由父親指向兒子,邊權是0,表示沒有區間的限制關係

.jpg)

這時,假設有一號位置的點限制了後面三個點,我們可以這樣連邊

![](~/getGraph (3).jpg)

然後,就和正常的一樣了,拓撲排序判一個正環啥的就行了,由於邊最多有klog(n) 個,複雜度近似就是O(klog(n))

#include <queue>
#include <stdio.h>
#include <string.h>
#include <iostream>
#include <algorithm>
const int N = 2000000;
const int M = 2000000;
const int inf = 1e9;
using namespace std;

int hash[N];

int tr[M];
int head[N];

char getc()
{
    static const int LEN = 1<<15;
    static char buf[LEN],*S=buf,*T=buf;
    if(S == T)
    {
        T = (S=buf)+fread(buf,1,LEN,stdin);
        if(S == T)return EOF;
    }
    return *S++;
}

int read()
{
    static char ch;
    static int D;
    while(!isdigit(ch=getc()));
    for(D=ch-'0'; isdigit(ch=getc());)
        D=(D<<3)+(D<<1)+(ch-'0');
    return D;
}


struct graph
{
    int next,to,val;
    graph () {}
    graph (int _next,int _to,int _val)
    :next(_next),to(_to),val(_val){}
}edge[M];

int in[M],MIN[M];

inline void add(int x,int y,int z)
{
    static int cnt = 0;
    edge[++cnt] = graph(head[x],y,z);
    in[y] ++;
    head[x] = cnt;
}

int cnt;

void build(int k,int l,int r)
{
    tr[k] = ++cnt;
    if(l==r){hash[l] = cnt;return;}
    int mid = (l+r)>>1;
    build(k<<1,l,mid);
    build(k<<1|1,mid+1,r);
    add(tr[k],tr[k<<1],0);
    add(tr[k],tr[k<<1|1],0);
}

void ask(int k,int l,int r,int x,int y)
{
    if(l==x && r==y){add(cnt,tr[k],1);return;}
    int mid = (l+r)>>1;
    if(y <= mid)ask(k<<1,l,mid,x,y);
    else if(x > mid)ask(k<<1|1,mid+1,r,x,y);
    else ask(k<<1,l,mid,x,mid),ask(k<<1|1,mid+1,r,mid+1,y);
}

queue <int> q;

int a[N];

int main()
{
    register int i,j;
    int n = read(),s = read(), m = read();
    build(1,1,n);
    for(i=1;i<=s;++i)
    {
        int tmp1 = read(),tmp2 = read();
        a[tmp1] = tmp2;
        MIN[hash[tmp1]] = tmp2;
    }
    for(i=1;i<=m;++i)
    {
        ++cnt;
        int l = read(),r = read(),k = read();
        int lastL = l - 1,lastR = r + 1;
        int pos;
        for(j=1;j<=k;++j)
        {
            pos = read();
            add(hash[pos],cnt,0);
            if(pos - lastL > 1)
                ask(1,1,n,lastL+1,pos-1);
            lastL = pos;
        }
        if(lastR - lastL > 1)
            ask(1,1,n,lastL + 1, lastR - 1);
    }

    for(i=1;i<=cnt;++i)
    {
        if(!MIN[i]) MIN[i] = inf;
        if(!in[i]) q.push(i);
    }

    while(!q.empty())
    {
        int tt = q.front();q.pop();
        for(i=head[tt];i;i=edge[i].next)
        {
            MIN[edge[i].to] = min(MIN[edge[i].to],MIN[tt] - edge[i].val);
            if(!--in[edge[i].to])
                q.push(edge[i].to);
        }
    }

    bool flag = 0;

    for(i=1;i<=cnt;++i)
        if(in[i])
            flag = 1;

    for(i=1;i<=n;++i)
        if(MIN[hash[i]] < 1)
            flag = 1;
    for(i=1;i<=n;++i)
        if(a[i] > MIN[hash[i]])
            flag = 1;
    if(flag)
        puts("NIE");
    else
    {
        puts("TAK");
        for(i=1;i<n;++i)
            printf("%d ",MIN[hash[i]]);
        cout << MIN[hash[n]] << endl;
    }

}

4382: [POI2015]Podział naszyjnika

Description
長度爲n的一串項鍊,每顆珠子是k種顏色之一。 第i顆與第i-1,i+1顆珠子相鄰,第n顆與第1顆也相鄰。
切兩刀,把項鍊斷成兩條鏈。要求每種顏色的珠子只能出現在其中一條鏈中。
求方案數量(保證至少存在一種),以及切成的兩段長度之差絕對值的最小值。

Solution

首先,考慮,如果只有兩種字符,我們只需要在遇到他的時候+1,再次遇到他的時候-1,然後前綴和相等的地方都是能取得。

現在變多了,我們可以這樣,還是維護一個Pre 表示這個東西上一次出現的位置,每次,如果他有一個Pre ,就加上一個數,然後自己減去相同的數字,然後前綴和相等的東西就都能取了。

由於串非常多,所以我們不能只是單純的+1-1,我們可以利用Hash ,然後map 判重就行了。

對於第一問,我們遍歷這個串,講與他顏色相同的所有位置扔到一個大隊列裏面,然後記錄一下一共有cnt 個,那麼這cnt 個鐘任意去都是可行的,所以貢獻是cnt(cnt1)/2

對於第二問,直接雙指針掃一遍就行了。

複雜度都在map 上………………

#include <map>
#include <stdio.h>
#include <string.h>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1100000+5;
const int seed = 2333333;
typedef unsigned long long ULL;

inline int read()
{
    int x=0,f=1;char ch = getchar();
    while(ch < '0' || ch > '9'){if(ch == '-')f=-1;ch = getchar();}
    while(ch >='0' && ch <='9'){x=(x<<1)+(x<<3)+ch-'0';ch = getchar();}
    return x*f;
}

map <ULL,int>mp;

ULL col[N],val[N],cnt;
ULL sum[N];
int pre[N],next[N],q[N],a[N],n,k;
bool vis[N];

int calc(int x,int y)
{
    int tt  = y - x;
    return abs(tt-(n-tt));
}

int main()
{
    col[0] = 1;
    n = read(), k = read();
    for(int i=1;i<=n;++i)col[i] = col[i-1] * seed;
    int tot = 0;
    for(int i=1;i<=n;++i) a[i] = read();

    for(int i=1;i<=n;++i)
    {
        if(pre[a[i]])
        {
            val[pre[a[i]]] += col[++tot];
            val[i] -= col[tot];
        }
        pre[a[i]] = i;
    }

    for(int i=1;i<=n;++i) sum[i] = sum[i-1] + val[i] ;

    for(int i=1;i<=n;++i)
    {
        if(mp[sum[i]])
            next[mp[sum[i]]] = i;
        mp[sum[i]] = i;
    }

    int ans = n;
    int tail = 0;
    for(int i=1;i<=n;++i)
        if(!vis[i])
        {
            tail = 0;
            for(int j=i;j;j=next[j])
                vis[j] = 1,q[++tail] = j;
    //      puts("");
            cnt += (ULL)(tail)*(tail-1)/2;
            for(int i=1,j=1;i<=tail;i++)
            {
                while(j < i && calc(q[j],q[i]) > calc(q[j+1],q[i])) ++j;
                ans = min(ans,calc(q[j],q[i]));
            }
        }
    cout << cnt << " " << ans << endl;

}

4377: [POI2015]Kurs szybkiego czytania

Description
給定n,a,b,p,其中n,a互質。定義一個長度爲n的01串c[0..n-1],其中c[i]==0當且僅當(ai+b) mod n < p。
給定一個長爲m的小01串,求出小串在大串中出現了幾次。

Solution

首先,gcd(a,n)=1 ,所以[0,n1] 的每個數字恰好出現一次。

我們可以設短串的首個與長串的匹配位置爲i1
x=ai1+b

如果短串的第i 個數爲0,那麼xai+n 不在區間[p,n1]

否則不在[0,p1] 中,然後我們就可以吧[0,n1] 不斷限制

因爲串長, ,所以i1 不在區間[nm+1,n1]

然後區間排個序,求個補集就行了

#include <stdio.h>
#include <string.h>
#include <iostream>
#include <algorithm>
const int N = 1100000+5;
using namespace std;

int cnt = 0,n,m,A,b,p;
int ans = 0;

struct interval
{
    int l,r;
    interval () {}
    interval (int _l,int _r):l(_l),r(_r){}
    bool operator<(const interval &z)const
    {
        return l < z.l;
    }
}a[N<<2];

void add(int l,int r){
    if(l<=r)a[++cnt] = interval(l,r);
    else a[++cnt] = interval(l,n-1),a[++cnt] = interval(0,r);
}

char s[N];

int main()
{
    cin >> n >> A >> b >> p >> m ;
    scanf("%s",s);
    for(int i=0,now = 0;i<m;++i,(now+=A)%=n)
        if(s[i] == '0')
            add((p-now+n)%n,(n-1-now+n)%n);
        else
            add((n-now+n)%n,(p+n-now-1)%n);
    for(int i=1,c=(b-A+n)%n;i<m;++i,c=(c-A+n)%n)
        add(c,c);
    sort(a+1,a+cnt+1);
    int tmp = -1;
    for(int i=1;i<=cnt;++i)
    {
        if(a[i].l > tmp) ans += a[i].l-tmp-1,tmp = a[i].r;
        else tmp = max(tmp,a[i].r);
    }
    cout << ans + n - 1 - tmp;
}

4384: [POI2015]Trzy wieże

sb BZ ,卡我常數,毀我青春

但是題還是不錯的題

Description
給定一個長度爲n的僅包含’B’、’C’、’S’三種字符的字符串,請找到最長的一段連續子串,使得這一段要麼只有一種字符,要麼有多種字符,但是沒有任意兩種字符出現次數相同

Solution

這題算是JOI 一道題的加強版的,但是強了很多……

JOI 是求相等的個數,我們維護sumi,sumc,sums 兩兩的差就行了,每次對應在map 上插入位置,然後每次更新答案就行了

這題我們可以也維護這三個東西,然後發現這其實是三維的東西,爭取固定兩維比較第三維

我們可以先把第一維排序,然後相同的第一維一起更新答案,再一起更新數據,處理了一維

第把第二維做成桶或者叫權值樹狀數組,維護差爲那個的時候座標的最大值,最小值

但是這樣還不夠,可能會出現第三維相同的情況,爲了規避這種情況,我們再維護一下最大值的第三維是什麼。

但是發現,如果衝突了也沒法解決,於是我們在維護一個次小值就行了,顯然這兩個的第二維不同

寫起來真是…………………………………………………………………………

#include <bits/stdc++.h>
using namespace std;
#define N 1100000
int n,lim,ans;
char s[N];
struct node 
{
    int a,b,c,pos;
    node(){}
    node(int a,int b,int c,int pos):a(a),b(b),c(c),pos(pos){}
    friend bool operator < (const node &r1,const node &r2)
    {return r1.a<r2.a;};
}p[N];
struct diw_tree
{
    int mx[N<<1],sx[N<<1],cx[N<<1],mn[N<<1],sn[N<<1],cn[N<<1];
    void init()
    {
        memset(mx,-0x3f,sizeof(mx));
        memset(sx,-0x3f,sizeof(sx));
        memset(mn,0x3f,sizeof(mn));
        memset(sn,0x3f,sizeof(sn));
    }
    void insert(int x,int y,int v)
    {
        for(int i=x;i<=lim;i+=i&-i)
        {
            if(v>mx[i])
            {
                if(cx[i]!=y)sx[i]=mx[i];
                mx[i]=v;cx[i]=y;
            }
            else if(y!=cx[i])
                sx[i]=max(sx[i],v);
            if(v<mn[i])
            {
                if(cn[i]!=y)sn[i]=mn[i];
                mn[i]=v;cn[i]=y;
            }
            else if(y!=cn[i])
                sn[i]=min(sn[i],v);
        }
    }
    inline void query(int x,int y,int v)
    {
        for(int i=x;i;i-=i&-i)
        {
            if(cx[i]!=y)ans=max(ans,mx[i]-v);
            else ans=max(ans,sx[i]-v);
            if(cn[i]!=y)ans=max(ans,v-mn[i]);
            else ans=max(ans,v-sn[i]);
        }
    }
}tr1,tr2;
int main()
{
    scanf("%d",&n);lim=(n+1)<<1;
    scanf("%s",s+1);
    for(int i=1,j;i<=n;i++)
    {
        j=i;
        while(s[j]==s[i]&&j<=n)j++;j--;
        ans=max(ans,j-i+1);i=j;
    }
    for(int i=1,a=0,b=0,c=0;i<=n;i++)
    {
        if(s[i]=='B')a++;
        if(s[i]=='C')b++;
        if(s[i]=='S')c++;
        p[i]=node(a-b,b-c,c-a,i);
    }
    tr1.init();tr2.init();
    sort(p,p+1+n);
    for(int i=0,j;i<=n;i++)
    {
        j=i;
        while(p[j].a==p[i].a&&j<=n)j++;j--;
        for(int k=i;k<=j;k++)
        {
            tr1.query(p[k].b-1+n+1,p[k].c,p[k].pos);
            tr2.query(n-(p[k].b+1)+1,p[k].c,p[k].pos);
        }
        for(int k=i;k<=j;k++)
        {
            tr1.insert(p[k].b+n+1,p[k].c,p[k].pos);
            tr2.insert(n-p[k].b+1,p[k].c,p[k].pos);
        }
        i=j;
    }
    printf("%d\n",ans);
    return 0;
} 

rank2jkxing 只寫了1k,1s秒過……,我問他寫了啥

他告訴我,一些奇怪的特判……
Orz

4381: [POI2015]Odwiedziny

Description
給定一棵n 個點的樹,樹上每條邊的長度都爲1,第i個點的權值爲a[i]
Byteasar 想要走遍這整棵樹,他會按照某個1到n的全排列b走n-1次,第i次他會從b[i]點走到b[i+1] 點,並且這一次的步伐大小爲c[i]
對於一次行走,假設起點爲x,終點爲y,步伐爲k,那麼Byteasar會從x開始,每步往前走k步,如果最後不足k步就能到達y,那麼他會一步走到y。
請幫助Byteasar 統計出每一次行走時經過的所有點的權值和。

Solution

調代碼的時候內心是崩潰的

好在開始寫的時候寫的不是很殘

首先,如果直接樹鏈剖分肯定是不行的[雖然這題確實能過

因爲極限情況其實會被卡成O(n2)?
不知道 沒算過 有算出更靠譜的可以下面留言

然後我們可以預處理一部分,正常的算法應該是像倍增預處理兩個東西

1.pos[i][j] 表示i 這個點向上走j 步的點是誰,顯然有pos[i][j]=pos[fa[i]][j1]

2.F[i][j] 表示每次跳j 個路徑上的權值的和,顯然有F[i][j]=F[pos[i][j]]+a[i]

然後我們可以處理出n 裏面的信息,對於詢問我們分類,一種是步長<n 的,我們可以直接前綴和搞一下

另外一種直接在樹剖上邊走邊統計就行了

這題真正的難點在於只能往上走,對於另外一邊的子樹,就會發生跨過lca 的情況,然後你就需要特判了,是不是恰好跳到lca 上啊,剩了多少步啊,現在還得跳多少步啊,能不能整除啊

寫出來就是幾行,幾個字節GG就夠你調一陣的了

另外閾值其實不一定非得是n ,由於數據是隨機數據,我們只需要調到logn 就行了,速度奇佳,現在是BZrank3

PS:經過多組測試,發現速度-閾值函數是一個單峯函數,在15左右取到速率的最大值



#include <stdio.h>
#include <string.h>
#include <iostream>
#include <algorithm>
const int N = 50000+5;
const int M = N << 1;
const int lmt = 15;
using namespace std;

int head[N];

struct graph
{
    int next,to;
    graph () {}
    graph (int _next,int _to)
    :next(_next),to(_to){}
}edge[M];

inline void add(int x,int y)
{
    static int cnt = 0;
    edge[++cnt] = graph(head[x],y);
    head[x] = cnt;
    edge[++cnt] = graph(head[y],x);
    head[y] = cnt;
}

int fa[N];
int PP[N][lmt+1];
int sum[N][lmt+1];
int depth[N],size[N],top[N];
int pos[N];
int belong[N];
int a[N];

void DFS1(int x)
{
    size[x] = 1;
    for(int i=2;i<=lmt;++i)
        if(PP[fa[x]][i-1])
            PP[x][i] = PP[fa[x]][i-1];
    for(int i=1;i<=lmt;++i)
        sum[x][i] = sum[PP[x][i]][i] + a[x];
    for(int i=head[x];i;i=edge[i].next)
        if(edge[i].to!=fa[x])
        {
            fa[edge[i].to] = x;
            depth[edge[i].to] = depth[x] + 1;
            PP[edge[i].to][1] = x;
            DFS1(edge[i].to);
            size[x] += size[edge[i].to];
        }
}

void DFS2(int x,int chain)
{
    static int cnt = 0;
    belong[pos[x] = ++cnt] = x;
    top[x] = chain;int k = 0;
    for(int i=head[x];i;i=edge[i].next)
        if(edge[i].to!=fa[x] && size[k] < size[edge[i].to])
            k = edge[i].to;
    if(!k) return; DFS2(k,chain);
    for(int i=head[x];i;i=edge[i].next)
        if(edge[i].to!=fa[x] && edge[i].to != k)
            DFS2(edge[i].to,edge[i].to);
}

int Lca(int x,int y)
{
    while(top[x] != top[y])
    {
        if(depth[top[x]] < depth[top[y]])swap(x,y);
        x = fa[top[x]];
    }
    return depth[x] < depth[y] ? x : y;
}

int up(int x,int y)
{
    while(depth[x] - depth[top[x]] < y)
    {
        y -= depth[x] - depth[top[x]] + 1;
        x = fa[top[x]] ;
    }
//  cout << belong[pos[x]-y] <<endl;
    return belong[pos[x] - y];
}

int Get(int x,int y,int v)
{
    if(v < lmt) return sum[x][v] - sum[y][v] + a[y];
    int ans = 0 , remain = 0 , t;
    while(top[x]!=top[y])
    {
        for(t = pos[x] - remain ; t >= pos[top[x]]; t -= v)
            ans += a[belong[t]];
        t += v;
        remain = v - ( t - pos[top[x]] + 1 );
        x = fa[top[x]];
    }

    for(t = pos[x] - remain;t>=pos[y]; t-= v)
        ans += a[belong[t]];
    return ans;

}

int calc(int x,int y,int v)
{
    int z = Lca(x,y);
    int ans = 0;
    int t = depth[x] - depth[z] - ( depth[x] - depth[z] )%v;
    t = up(x,t);
    ans = Get(x,t,v);
    if(y == z)
        return t == y ? ans : a[y];
    int t1 = v - (depth[t] - depth[z]);
    int t2 = depth[y] - depth[z] - t1;
    if(t2 < 0) return ans + a[y];
    int t3 = up(y,t2%v);
    int t4 = up(y,t2);
    ans += Get(t3,t4,v);
    return t2%v ? ans + a[y] : ans;
}

inline int read()
{
    int x=0,f=1;char ch = getchar();
    while(ch < '0' || ch > '9'){if(ch == '-')f=-1;ch = getchar();}
    while(ch >='0' && ch <='9'){x=(x<<1)+(x<<3)+ch-'0';ch = getchar();}
    return x * f;
}

int b[N];

int main()
{
    int n = read();
    for(int i=1;i<=n;++i) a[i] = read();
    for(int i=1;i<n;++i)
    {
        int x = read(),y = read();
        add(x,y);
    }
    DFS1(1);
    DFS2(1,1);
/*  for(int i=1;i<=n;++i)
    {
        for(int j=1;j<=3;++j)
            cout << sum[i][j] << " ";
        puts("");
    }*/
    for(int i=1;i<=n;++i) b[i] = read();
    for(int i=1;i<n;++i){
        int x = read();
        printf("%d\n",calc(b[i],b[i+1],x));
    }
}

4380: [POI2015]Myjnie

Description
有n家洗車店從左往右排成一排,每家店都有一個正整數價格p[i]。
有m個人要來消費,第i個人會駛過第a[i]個開始一直到第b[i]個洗車店,且會選擇這些店中最便宜的一個進行一次消費。但是如果這個最便宜的價格大於c[i],那麼這個人就不洗車了。
請給每家店指定一個價格,使得所有人花的錢的總和最大。

Solution

首先,我們可以把他的價格離散,然後用 f(i,j,k) 表示從 ij 的區間的最小值爲 k ,然後我們枚舉最小值所在的位置,再處理左邊和右邊,也就是

f(i,j,k)=f(i,pos1,cost1)+val+f(pos+1,j,cost2),cost1,cost2

然後我們發現可以區間dp
爲了方便轉移,再維護一個後綴的最大值就行了
最後遞歸輸出一下

#include <stdio.h>
#include <string.h>
#include <iostream>
#include <algorithm>
typedef long long LL;
using namespace std;

const int N = 50+2;
const int M = 4000+2;

int n,m,cnt;

int l[M],r[M],c[M],a[M];
int pos[N][N][M],val[N][N][M];

LL f[N][N][M],sum[N][N][M];
int V[M];

void print(int l,int r,int v)
{
    if(r < l) return;
    int tt = val[l][r][v];
    print(l,pos[l][r][v]-1,tt);
    printf("%d ",V[tt]);
    print(pos[l][r][v]+1,r,tt);
}

inline int read()
{
    int x = 0, f = 1; char ch = getchar();
    while (ch < '0' || ch > '9') { if (ch == '-') f = -1; ch = getchar(); }
    while (ch >= '0' && ch <= '9') { x = x * 10 + ch - '0'; ch = getchar(); }
    return x * f;
}
int stack[M];

int main()
{
    cin >> n >> m;
    register int k;
    for(int i=1;i<=m;++i) l[i] = read(),r[i] = read(),V[i] = c[i] = read();
    int top;
    sort(V+1,V+m+1);
    for(int i=1;i<=m;++i)
        if(V[i]!=V[i-1])
            V[++cnt] = V[i];
    for(int i=0;i<n;++i)
        for(int j=1;j+i<=n;++j)
        {
            int ri = j + i;
            for(k=j;k<=ri;++k)
            {
                top = 0;
                for(int t=1;t<=m;++t)
                    if(l[t] >= j && r[t] <= ri && l[t] <= k && r[t] >= k)
                        stack[++top] = c[t];
                sort(stack+1,stack+top+1);
                for(int t=1,now=1;t<=cnt;++t)
                {
                    while(now <= top && stack[now] < V[t]) ++ now;
                    LL t1 = sum[j][k-1][t] + sum[k+1][ri][t] + (LL)V[t]*(top-now+1);
                    if(t1 >= f[j][ri][t])
                    {
                        f[j][ri][t] = t1;
                        pos[j][ri][t] = k;
                    }
                }
            }

            for(k=cnt;k>=1;--k)
            {
                val[j][ri][k] = k;
                if(f[j][ri][k] < sum[j][ri][k+1])
                {
                    pos[j][ri][k] = pos[j][ri][k+1];
                    val[j][ri][k] = val[j][ri][k+1];
                }
                sum[j][ri][k] = max(sum[j][ri][k+1],f[j][ri][k]);
            }
        }
    cout << sum[1][n][1] << endl;
    print(1,n,1);
}

3747- [POI2015]Kinoman

Description
共有m部電影,編號爲1~m,第i部電影的好看值爲w[i]。

在n天之中(從1~n編號)每天會放映一部電影,第i天放映的是第f[i]部。

你可以選擇l,r(1<=l<=r<=n),並觀看第l,l+1,…,r天內所有的電影。如果同一部電影你觀看多於一次,你會感到無聊,於是

無法獲得這部電影的好看值。所以你希望最大化觀看且僅觀看過一次的電影的好看值的總和。

Solution
我們可以枚舉左端點線段樹處理右端點
考慮到對next[i]next[next[i]] 的影響就行了
具體可以參見HH的項鍊一題

#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <iostream>
#define N 1000000+5
#define M 4000000+5
typedef long long LL;
using namespace std;

LL tr[M],lazy[M];


inline void updata(int k)
{
    tr[k] = max(tr[k<<1],tr[k<<1|1]);
}

inline LL read()
{
    LL x=0,f=1;char ch = getchar();
    while(ch < '0' || ch > '9'){if(ch == '-')f=-1;ch = getchar();}
    while(ch >='0' && ch <='9'){x=(x<<1)+(x<<3)+ch-'0';ch = getchar();}
    return x*f;
}

inline void down(int k,int l,int r)
{
    if(l==r || !lazy[k])return;
    LL tmp = lazy[k];lazy[k] = 0;
    tr[k<<1] += tmp;tr[k<<1|1] += tmp;
    lazy[k<<1] += tmp;lazy[k<<1|1]+=tmp;
}

inline void change(int k,int l,int r,int x,int y,LL c)
{
    down(k,l,r);
    if(l==x && r==y){lazy[k] += c;tr[k] += c;return;}
    int mid = (l+r)>>1;
    if(y<=mid)change(k<<1,l,mid,x,y,c);
    else if(x>mid)change(k<<1|1,mid+1,r,x,y,c);
    else change(k<<1,l,mid,x,mid,c),change(k<<1|1,mid+1,r,mid+1,y,c);
    updata(k);
}

LL a[N],b[N];
LL next[N],last[N];

int main()
{
    int n = read(), m =read();
    for(int i=1;i<=n;++i)
        a[i] = read();
    for(int i=1;i<=m;++i)
        b[i] = read();
    for(int i=n;i>=1;--i)
        next[i] = last[a[i]],last[a[i]] = i;
    for(int i=1;i<=m;++i)
        if(last[i])
        {
            if(!next[last[i]])
                change(1,1,n,last[i],n,b[i]);
            else
                change(1,1,n,last[i],next[last[i]]-1,b[i]);
        }
    LL ans = 0;
    for(int i=1;i<=n;++i)
    {
        ans = max(ans,tr[1]);
        int t = next[i];
        if(t)
        {   
            change(1,1,n,i,t-1,-b[a[i]]);
            if(next[t])
                change(1,1,n,t,next[t]-1,b[a[i]]);
            else
                change(1,1,n,t,n,b[a[i]]);
        }
        else 
            change(1,1,n,i,n,-b[a[i]]);
    }
    cout<<ans<<endl;
}

3750: [POI2015]Pieczęć

Description
一張n*m的方格紙,有些格子需要印成黑色,剩下的格子需要保留白色。
你有一個a*b的印章,有些格子是凸起(會沾上墨水)的。你需要判斷能否用這個印章印出紙上的圖案。印的過程中需要滿足以下要求:
(1)印章不可以旋轉。
(2)不能把墨水印到紙外面。
(3)紙上的同一個格子不可以印多次。
Solution
mdzz
這是POI2015最水沒有之一,顯然現在途中最左上的點必然對應着一個印章的最左上

然後 思博模擬

#include <stdio.h>
#include <string.h>
#include <iostream>
#include <algorithm>
const int N = 1000+5;
using namespace std;

struct data
{
    int x,y;
    data () {}
    data (int _x,int _y)
    :x(_x),y(_y){}
}p[N*N];

int cnt;
char s1[N][N],s2[N][N];

int main()
{
    int T;
    cin >> T;
    int n,m,a,b;
    while(T--)
    {
        cin >> n >> m >> a >> b;
        for(int i=1;i<=n;++i)scanf("%s",s1[i]+1);
        cnt = 0;
        for(int i=1;i<=a;++i)
        {
            scanf("%s",s2[i]+1);
            for(int j=1;j<=b;++j)
                if(s2[i][j] == 'x')
                    p[++cnt] = data(i,j);   
        }
        bool flag = false;
        for(int i=1;i<=n;++i)
        {
            for(int j=1;j<=m;++j)
            {
                if(s1[i][j] == 'x')
                {
                    for(int k=1;k<=cnt;++k)
                    {
                        data t1 = data(i+p[k].x-p[1].x,j+p[k].y-p[1].y);
                        if(t1.x > n || t1.y > m )
                        {
                            flag = 1;
                            break;
                        }
                        if(s1[t1.x][t1.y] != 'x')
                        {
                            flag = 1;
                            break;
                        }
                        s1[t1.x][t1.y] = '.';
                    }
                }
                if(flag) break;
            }
            if(flag) break;
        }
        if(flag)
            puts("NIE");
        else puts("TAK");
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章