【noip2016模拟day1】【线段树】【dp】

复制&粘贴2(A.c/cpp/pas/in/out)
(Time Limit:1s Memory Limit:256MB)
【Description】
文本编辑器的一个最重要的机能就是复制&粘贴。JOI社现在正在开发一款能够非常高速地进行复制&粘贴的文本编辑器,作为JOI社一名优秀的程序猿,你担负起了复制&粘贴功能的测试这一核心工作。整个JOI社的命运都系在你的身上,因此你无论如何都想写出一个正确且高速的程序来完成这项工作。
具体的做法如下所示。文件的内容是一个字符串S,对其进行N次复制&粘贴的操作,第i次操作复制位置Ai和位置Bi之间的所有文字,然后在位置Ci粘贴。这里位置x表示字符串的第x个字符的后面那个位置(位置0表示字符串的开头),例如字符串”copypaste”的位置6表示字符’a’和字符’s’之间的位置,位置9表示’e’后面的位置(即字符串的结尾)。不过,如果操作后的字符串长度超过了M,那么将超过的部分删除,只保留长度为M的前缀。
你的任务是写一个程序,输出N次操作后字符串的前K个字符。
【Input】
第一行两个空格分隔的正整数K和M,表示最终输出的文字数量和字符串长度的上限。
第二行一个字符串S,表示初始的字符串。
第三行一个整数N,表示操作的次数。
接下来N行,第i行包含三个空格分隔的整数Ai,Bi,Ci,表示第i次操作将位置Ai到Bi之间的字符串复制,然后粘贴到位置Ci。
【Output】
输出一行一个长度为K的字符串,表示最终的字符串的长度为K的前缀
【Sample Input】
2 18
copypaste
4
3 6 8
1 5 2
4 12 1
17 18 0
【Sample Output】
ac
【HINT】
初始的字符串为”copypaste”。
第一次操作将从位置3到位置6的字符串”ypa”复制,插入位置8,得到字符串”copypastypae”
第二次操作将从位置1到位置5的字符串”opyp”复制,插入位置2,得到字符串”coopyppypastypae”
第三次操作将从位置4到位置12的字符串”yppypast”复制,插入位置1,得到字符串”cyppypastoopyppypastypae”,由于长度超过了M=18,删除超过的部分,得到”cyppypastoopyppypa”
第四次操作将从位置17到位置18的字符串”a”复制,插入位置0,得到字符串”acyppypastoopyppypa”,删除超过18的部分得到”acyppypastoopyppyp”
最后输出长度为2的前缀”ac”即为答案。
【Data Constraint】
对于40%的数据,N,M<=2000
对于100%的数据:
1<=K<=200
1<=M<=10^9
S的每个字符都是小写字母(‘a’~’z’)
K<=|S|<=min(M,2*10^5)
1<=N<=2*10^5
设第i次操作前的字符串长度为Li,那么0<=Ai < Bi<=Li且0<=Ci<=Li (1<=i<=N)


这是一个dp题,这居然是一个dp题!!!
f[i][j]表示最终j位置上的元素在经过i次操作后的位置,倒推就ok
递归代码(不知为何超时):

#include<iostream>      
#include<cstdio>
#include<cstring>
#define LL long long
using namespace std;
const int N=2e5+5;
char s[N];
int a[N],b[N],c[N],k,m,n;
char get(int id,int x)
{
    if(!id)return s[x];
    if(x<=c[id]) return get(id-1,x);
    else if(x>c[id]&&x<=c[id]+b[id]-a[id])return get(id-1,a[id]+x-c[id]);
    else return get(id-1,x-(b[id]-a[id]));
}
int main()
{
    freopen("A.in","r",stdin);
    freopen("A.out","w",stdout);
    scanf("%d%d",&k,&m);
    scanf("%s",s+1);scanf("%d",&n);
    for(int i=1;i<=n;i++)
        scanf("%d%d%d",&a[i],&b[i],&c[i]);
    for(int i=1;i<=k;i++)
    printf("%c",get(n,i));
    return 0;
}

递推代码(不会超时):

#include<iostream>       
#include<cstdio>
#include<cstring>
#define LL long long
using namespace std;
const int N=2e5+5;
char s[N];
int a[N],b[N],c[N],k,m,n,f[2][205],now,pre;
int main()
{
    freopen("A.in","r",stdin);
    freopen("A.out","w",stdout);
    scanf("%d%d",&k,&m);
    scanf("%s",s+1);scanf("%d",&n);
    for(int i=1;i<=n;i++)
        scanf("%d%d%d",&a[i],&b[i],&c[i]);
    for(int i=1;i<=k;i++)f[0][i]=i;
    now=1;
    for(int j=n;j>=1;j--)
    {
        for(int i=1;i<=k;i++)
        {
            if(f[pre][i]<=c[j])f[now][i]=f[pre][i];
            else if(f[pre][i]>c[j]&&f[pre][i]<=c[j]+b[j]-a[j])
                    f[now][i]=a[j]+f[pre][i]-c[j];
            else f[now][i]=f[pre][i]-(b[j]-a[j]);
        }
        swap(now,pre);
    }
    for(int i=1;i<=k;i++)
    printf("%c",s[f[pre][i]]);
    return 0;
}

代码就是不蓝TAT,又是这样欺负我TAT


T2

愉快的logo设计(B.c/cpp/pas/in/out)
(Time Limit:1s Memory Limit:256MB)
【Description】
K理事长正在思考日本信息学奥林匹克竞赛选手的应援道具的logo问题。某天,K理事长突发奇想,想要设计一个用’J’,’O’,’I’三种文字环形排列的logo,意为希望选手能从JOI中收获快乐的意思。
(注:“环形地”在日文中的表述为“円状に”,“円”读作“en”,再加上“JOI”三个字即为“enjoy”„„)
如下所示,对于任意非负整数k,我们定义标号为k的JOI序列Sk为:
·S0为’J’,’O’,’I’中任一字符构成的长度为1的字符串
·S[k+1]为最初4^k个字符都是’J’,接下来的4^k个字符都是’O’,接下来的4^k个字符都是’I’,最后4^k个字符是字符串Sk的长为4^(k+1)的字符串
现在,K理事长在纸上写下了由4^K个文字构成的一个环形字符串,字符串中每个字符都是’J’,’O’,’I’中的一个。K理事长想要修改一些文字,使得得到的字符串从某个起点开始顺时针读一圈后可以得到SK。在满足条件的情况下,要求修改的文字数量最少。
【Input】
第一行一个正整数K,表示K理事长在纸上写下了一个长度为4^K的环状字符串。
第二行一个由’J’,’O’,’I’三个字符构成的长为4^K的字符串,表示纸上的环形字符串从某个起点出发顺时针阅读一圈得到的字符串。
【Output】
输出一行一个整数,表示修改文字数量的最小值。
【Sample Input】
2
JJOIJJOJOIOJOOOI
【Sample Output】
7
【HINT】
从○标记的位置顺时针阅读一圈得到“JJJJOOOOIIIIJOIJ”,满足S2的条件,且修改文字数达到最小值7。
【Data Constraint】
对于30%的数据,1<=K<=5
对于100%的数据,1<=K<=10

此题思路来自于飞哥(还缺一个小佛orz),因为是环状,自然而然想到拉直它变成两倍长,对三种字符的匹配数做前缀和,由匹配数算出不匹配数,复杂度是(4^k)*k.
ps:把每一段J、O、I分别看做一个区间,做统计,再把最后一个区间瓜分成小区间继续统计直到最后只剩一个元素它一定满足√
代码如下:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#define LL long long
using namespace std;
const int N=(1<<21)+5;
int sum[N][3],k,n,st,temp,ans,tot;
char s[N];
void pre()
{
    n=1<<(k*2);
    for(int i=0;i<=1;i++)
    for(int j=1;j<=n;j++)
    {
        sum[i*n+j][0]=sum[i*n+j-1][0];
        sum[i*n+j][1]=sum[i*n+j-1][1];
        sum[i*n+j][2]=sum[i*n+j-1][2];
        if(s[j]=='J')sum[i*n+j][0]++;
        if(s[j]=='O')sum[i*n+j][1]++;
        if(s[j]=='I')sum[i*n+j][2]++;
    }
}
int main()
{
    freopen("B.in","r",stdin);
    freopen("B.out","w",stdout);
    scanf("%d",&k);scanf("%s",s+1);
    pre();
    ans=n;
    for(int i=0;i<n;i++)   //用前缀和处理匹配数* 
    {
        temp=n>>2;st=i;tot=0;
        while(temp)  //匹配数 
        {
            tot+=sum[st+temp][0]-sum[st][0];
            tot+=sum[st+temp*2][1]-sum[st+temp][1];
            tot+=sum[st+temp*3][2]-sum[st+temp*2][2];
            st+=temp*3;
            temp>>=2;
        }
        ans=min(ans,n-tot-1);
    }
    printf("%d\n",ans);
    return 0;
}

T3

有趣的有趣的家庭菜园(C.c/cpp/pas/in/out)
(Time Limit:1s Memory Limit:256MB)
【Description】
职业经营家庭菜园的JOI君每年在自家的田地中种植一种叫做IOI草的植物。IOI草的种子在冬天被播下,春天会发芽并生长至一个固定的高度。到了秋天,一些IOI草会结出美丽的果实,并被收获,其他的IOI草则会在冬天枯萎。
JOI君的田地沿东西方向被划分为N个区域,从西侧开始的第i个区域中种植着IOI草i。在第i个区域种植的IOI草,在春天的时候高度会生长至Hi,此后便不再生长。如果IOI草i会结出果实,那么将会获得Pi的收益,否则没有收益。
春天到了,查看田地样子的JOI君决定拔掉一些种植的IOI草,使利益最大化。拔掉IOI草i需要Ci的花销,拔掉的IOI草会立刻枯萎。IOI草只能在春天被拔掉,夏天和秋天不能拔掉IOI草。
IOI草是一种非常依靠阳光的植物,如果在夏天某个区域的IOI草的东侧和西侧都有比它高的IOI草存在,那么这株IOI草在秋天便不会结出果实。换句话说,为了让没有被拔掉的IOI草i在秋天结出果实,到了夏天的时候,以下两个条件至少满足一个:
1.对于任意1<=j<=i-1,Hj<=Hi或IOI草j已经被拔除
2.对于任意i+1<=j<=N,Hj<=Hi或IOI草j已经被拔除
用最终收获的果实的总价格减掉拔除IOI草的花销的总和,即为JOI君的收益。那么JOI君能从IOI草中获取的最大利益到底有多少呢?
【Input】
第一行一个正整数N,表示田地被分为了N个区域。
接下来N行,第i行(1<=i<=N)三个空白分割的正整数Hi,Pi,Ci,表示第i株IOI草在春天时高度会生长至Hi,秋天收获的果实的价格为Pi,拔除所需费用为Ci。
【Output】
输出一行一个整数,表示JOI君能获得的最大利益
【Sample Input】
7
22 60 30
46 40 30
36 100 50
11 140 120
38 120 20
24 90 60
53 50 20
【Sample Output】
320
【HINT】
拔除IOI草2和IOI草7,剩余的IOI草如下图所示:
IOI草1、3、5、6的果实价格分别为60、100、120、90,拔除IOI草2和IOI草7的花销分别为30、20,总收益为320,这是所有方案中的最大值。
【Data Constraint】
对于30%的数据,N<=20
对于45%的数据,N<=300
对于60%的数据,N<=5000
对于100%的数据:
3<=N<=10^5
1<=Hi<=10^9 (1<=i<=N)
1<=Pi<=10^9 (1<=i<=N)
1<=Ci<=10^9 (1<=i<=N)

这道题想到是线段树但在处理上卡壳了,正解如下:
将height离散化,放在线段树上,f[i]=max{f[i]+wor[i]-拔草}
因为越往后拔草值是根据高度不同累加,所以在每次从线段树找出最大(f-拔草)值后,将小于h[i]的值都-reduce[i],因为在后面取这些小于h[i]值的时候,必定会拔掉i这棵草,好吧有点抽象orz

代码如下:

#include<iostream>
#include<cstdio>
#include<algorithm>
#define R(x) (x<<1|1)
#define L(x) (x<<1)
#define LL long long
using namespace std;
const int N=1e5+5;
LL INF=-1e14;
int n,he[N],tot;
LL f[N],t[N],ans,mm;
struct node{
    int red,wor,he,num;
    int hi;
}gra[N];
struct node2{
    int l,r;
    LL ma,js;
}tr[N*4];
bool comp1(const node& a,const node&b)
{return a.he<b.he;}
bool comp2(const node& a,const node&b)
{return a.num<b.num;}
LL max(LL a,LL b)
{if(a>=b)return a;else return b;}
void update(int u)
{tr[u].ma=max(tr[L(u)].ma,tr[R(u)].ma);}
void down(int u)
{
    tr[L(u)].ma-=tr[u].js;
    tr[L(u)].js+=tr[u].js;
    tr[R(u)].ma-=tr[u].js;
    tr[R(u)].js+=tr[u].js;
    tr[u].js=0;
}
void build(int u,int le,int ri)
{
    tr[u].l=le;tr[u].r=ri;tr[u].ma=0;tr[u].js=0;
    if(le==ri)return;
    int mid=(le+ri)>>1;
    build(L(u),le,mid);
    build(R(u),mid+1,ri);
}
void exch(int u,int k,LL q)
{
    if(tr[u].l==tr[u].r&&tr[u].l==k)
    {
        tr[u].ma=max(tr[u].ma,q);
        return;
    }
    if(tr[u].js)down(u);
    int mid=(tr[u].l+tr[u].r)>>1;
    if(mid>=k)exch(L(u),k,q);
    else exch(R(u),k,q);
    update(u);
}
void add(int u,int le,int ri,LL w)
{
    if(le==tr[u].l&&tr[u].r==ri)
    {
        tr[u].js+=w;
        tr[u].ma-=w;
        return;
    }
    if(tr[u].js)down(u);
    int mid=(tr[u].l+tr[u].r)>>1;
    if(mid>=ri)add(L(u),le,ri,w);
    else if(mid<le)add(R(u),le,ri,w);
    else{
        add(L(u),le,mid,w);
        add(R(u),mid+1,ri,w);
    }
    update(u);
}
void sear(int u,int le,int ri)
{
    if(le==tr[u].l&&tr[u].r==ri)
    {
        mm=max(tr[u].ma,mm);
        return;
    }
    if(tr[u].js)down(u);
    int mid=(tr[u].l+tr[u].r)>>1;
    if(mid>=ri)sear(L(u),le,ri);
    else if(mid<le)sear(R(u),le,ri);
    else{
        sear(L(u),le,mid);
        sear(R(u),mid+1,ri);
    }
    update(u);
}
void pre()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%d%d%d",&gra[i].he,&gra[i].wor,&gra[i].red);
        gra[i].num=i;
    }
    sort(gra+1,gra+n+1,comp1);
    for(int i=1;i<=n;i++)
    if(gra[i].he==gra[i-1].he)gra[i].hi=tot;
    else gra[i].hi=++tot;
    sort(gra+1,gra+n+1,comp2);
    build(1,1,tot);
}
void worrk()
{
    for(int i=1;i<=n;i++)
    {
        mm=INF;
        sear(1,1,gra[i].hi);
        f[i]=mm+(LL)gra[i].wor;
        if(gra[i].hi>1)add(1,1,gra[i].hi-1,gra[i].red);
        exch(1,gra[i].hi,f[i]);
    }
    build(1,1,tot);
    for(int i=n;i>=1;i--)
    {
        mm=INF;
        sear(1,1,gra[i].hi);
        t[i]=mm+(LL)gra[i].wor;
        if(gra[i].hi>1)add(1,1,gra[i].hi-1,gra[i].red);
        exch(1,gra[i].hi,t[i]);
        ans=max(ans,t[i]+f[i]-(LL)gra[i].wor);
    }
}
int main()
{
    freopen("C.in","r",stdin);
    freopen("C.out","w",stdout);
    pre();
    worrk();
    cout<<ans<<endl;
}

注意比较大小的时候是t[i]+f[i]-(LL)gra[i].wor而不是ans=max(ans,t[i]+f[i-1]);


无关紧要的感叹:
noip还有四天了,fighting!!!尽全力就好√=^ω^=

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值