POI 2015 题解

6 篇文章 0 订阅

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<j f(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 比另一个数字 B C , 那么就从 A B 连一条边权为C的边.

2.对图进行拓扑排序,把起点的值设为 inf ,不断限制中间的点的最大值

3.如果有正环或者有点的范围不在规定的范围之内就 GG

然而这题的点数太多了,耿直的建边是 n2 级别的,所以我们需要一些技♂巧

我们可以学习 Vfleaking A+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;
} 

rank2 jkxing 只写了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 <script type="math/tex" id="MathJax-Element-96"><\sqrt{n}</script>的,我们可以直接前缀和搞一下

另外一种直接在树剖上边走边统计就行了

这题真正的难点在于只能往上走,对于另外一边的子树,就会发生跨过 lca 的情况,然后你就需要特判了,是不是恰好跳到 lca 上啊,剩了多少步啊,现在还得跳多少步啊,能不能整除啊

写出来就是几行,几个字节GG就够你调一阵的了

另外阈值其实不一定非得是 n ,由于数据是随机数据,我们只需要调到 logn 就行了,速度奇佳,现在是 BZ rank3

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) 表示从 i j 的区间的最小值为 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");
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值