两粒种子,一片森林 jzoj 2017.8.22 B组

第一题

  1. 【NOIP2013模拟联考1】压缩(compress)
    (File IO): input:compress.in output:compress.out
    Time Limits: 1000 ms Memory Limits: 262144 KB Detailed Limits Special Judge
    Goto ProblemSet

Description

巨大的文本往往令人头疼,特别是文本内容有大量重复的情况下,巨大的文本不便于运输和阅读,于是我们提出了NOIP(Nonsense Obivous Index Pattern(荒谬的显然索引法)),一种“有效的”压缩文本的方法。

NOIP压缩后的格式很特别,一个文本压缩后由若干个单元组成,每个单元由3部分组成:1.正文(小写字母组成的字符串)2.若干个”*”,表示正文的又重复了几次3.单元的结尾符号”$”

比如,NOIP压缩后的文本hello** yes no****$的含义是”hello”重复3次,yes重复1次,no重复5次,解压后就变成hellohellohelloyesnonononono。

显然,对于同一文本,压缩后的表示方法不唯一,但是为了方便,我们要求你采用压缩后字符串最短的压缩方法,如果有多种压缩方法,只需输出任意一种。

Input

一个字符串,只含小写字母,表示原来的文本。

Output

一个字符串,表示一种最短的压缩后文本。

Sample Input

输入1:

aaaa

输入2:

hellohellohelloyesnonononono

Sample Output

输出1:

aa*$

【样例解释1】

除此外还有多种压缩方法,但是长度都比样例输出长,以下列举其中几种:

a***$

aaaa$

a** a

输出2:

hello**yesno****$

Data Constraint

记len为读入的字符串长度。

20%的数据,len<=10

70%的数据,len<=200

100%的数据,len<=2000

第二题

  1. 【NOIP2013模拟联考1】数字串(digit)
    (File IO): input:digit.in output:digit.out
    Time Limits: 1000 ms Memory Limits: 262144 KB Detailed Limits
    Goto ProblemSet

Description

相信大家都见过这个数字串“012345678910111213141516171819202122……”有一个问题,是请你求出这个串的第a位到第b位的和。某蒟蒻认为此数字串不够优美,他把0、1、2、3、4、5、6……都变成了二进制数,再把它们拼在了一起:011011100101110111100010011010……。蒟蒻不愧为巨弱,他完全无法求出这个串第a位到第b位的和,请你帮帮他。

Input

为了避免读入耗费太多时间,输入文件只有8个整数参数q,a[1],b[1],k1,k2,k3,k4,M。由下面的程序生成q个询问(ai,bi)。

所有输入的数均为不超过1013的非负整数。1≤k1,k3≤100000,1≤k2,k4≤2147483647。M为正数。

// for pascal

readln(q,a[1],b[1],k1,k2,k3,k4,M);

for i:=2 to q do

a[i]:=(int64(a[i-1])*k1+k2) mod 11111123111111;

for i:=2 to q do

b[i]:=(int64(b[i-1])*k3+k4) mod 11111123111111;

for i:=1 to q do

a[i]:=a[i] mod M+1;

for i:=1 to q do

b[i]:=b[i] mod M+1;

for i:=1 to q do

if a[i]>b[i] then

begin

t:=a[i];

a[i]:=b[i];

b[i]:=t;

end;

// for C/C++

scanf(“%d%d%d%d%d%d%d%d”,&q,a+1,b+1,&k1,&k2,&k3,&k4,&M);

for (int i=2;i<=q;i++)

a[i]=((long long)a[i-1]*k1+k2) % 11111123111111LL;

for (int i=2;i<=q;i++)

b[i]=((LL)b[i-1]*k3+k4) % 11111123111111LL;

for (int i=1;i<=q;i++)

a[i]=a[i]%M+1;

for (int i=1;i<=q;i++)

b[i]=b[i]%M+1;

for (int i=1;i<=q;i++)

if (a[i]>b[i])

{

t=a[i];

a[i]=b[i];

b[i]=t;

}

Output

Sample Input

输入1:

3 1 10 1 2 3 4 101

输入2:

10 1 10 1 2 3 4 9001

Sample Output

输出1:

188

输出2:

788167003

Data Constraint

这里写图片描述

Hint
这里写图片描述

贴官方题解:
第二题:数字串
想法零:暴力,送你20分;
我们注意到询问区间[a,b]等同于询问[1,b]-[1,a-1]。
于是问题转化为询问[1,x]的数字和。
想法一:记录一下前缀和,输出的时候做一下减法,送你50分;
想法二:压位然后记录前缀和。
①30位一压,常数比较大,勉强70分。
②按照每个数来压位,轻松70分。
时间:O(QlogM)
空间:O(M/logM)
想法三:标程解法
我们注意到对于所有长度为n(n>=2)的二进制,他们的数字和是2^(n-1)+(n-1)*2^(n-2)。
于是我们可以通过二分答案求得x的长度。对于所有长度小于x的,用公式计算。
对于长度等于x的,我们可以知道第一位一定是1,那么就先统计了第一位,这是就相当于删掉了第一位。
把剩下的数的前导0删除,那么又回到了上面的那个问题。
那么就可以递归进行计算。
时间:O(QlogMlogM)
空间:O(1)

顺带一提,题目里面的读入十分耗空间,我们不应该直接复制代码。

第三题

  1. 【NOIP2013模拟联考1】炸毁城市(city) (Standard IO)
    Time Limits: 1000 ms Memory Limits: 262144 KB Detailed Limits
    Goto ProblemSet

Description

n个城市和n-1条道路构成了一个连通图(树),炸毁一座城市可以使得所有与它相连的道路失去使用价值。请问至少要炸掉多少个城市,才可以使n-1条道路全部失去使用价值呢?若执行某个最优方案,哪些城市是无论如何都不可能被炸毁的呢?

Input

第一行一个整数n,表示有n个城市。

接下来n-1个,每行两个数a、b,表示a、b城市间有一条道路。

Output

第一行一个数,表示最少需要炸掉的城市数量。

第二行若干个数,表示一定不会被摧毁的城市,一个空格隔开。按照递增顺序输出。

建议行末不要有空格。若没有城市一定不会被摧毁,在第二行输出一个空行。

Sample Input

输入1:

5

1 2

1 3

2 4

2 5

输入2:

14

1 2

1 3

1 6

2 5

3 4

6 7

6 8

6 9

9 10

9 14

10 11

14 12

14 13

Sample Output

输出1:

2

4 5

输出2:

5

1 4 5 7 8 9 11 12 13

Data Constraint

对于10%的数据,1≤n≤15;

对于50%的数据,1≤n≤1000;

对于100%的数据,1≤n≤20000。

题解:第一问可以用暴力,关键第二问,倘若第一问用了贪心的方法,第二问将无比艰难。贪心时连边相等的点会有多
个,我们难以得知哪些点是一定要炸,哪些点是一定不炸,哪些点是可炸可不炸。
于是,另辟蹊径。
观察这棵树。倘若我们不炸点 2,那么想要去掉
(2,4)(2,5)边,点 4 和点 5 则一定要炸。此时树
形 dp 的模型已经出现。(详情见模板题:没有上司
的舞会,图为样例一的图)
这里写图片描述

设 f [i] 表示当前计算到点 i,且对于点 i 不炸,已
经炸了的最少的城市去除了其下的所有边。
设 g[i] 表示当前计算到点 i,且对于点 i 一定炸,已
经炸了的最少的城市去除了其下的所有边。
得出 dp 方程:
f [i] =sum(g[k])
g[i] = sum(min( f [k], g[k])) +1 (这里加一是因为自己要炸啊)
ps:把sum当做求和符号
k 为 i 的子节点
第一问答案为 min( f [1], g[1])

第二问利用第一问计算的结果,可以从根递推回叶子节点。
若父节点一定不炸,那么子节点必然要炸。
若父节点可炸可不炸,则若子节点 f [i]>g[i] ,则该子节点必然要炸;而若 f [i] <= g[i],即使有 f [i] < g[i]的情况,由于父节点的不确定性,不能确定子节点一定不炸(因为父节点有不炸的选择,所以子节点也有必炸的选择),所以不确定性顺延,该节点可炸可不炸。
若父节点一定要炸,则子节点与父节点脱离关系,变成一颗子树,重新按根节点处理。
根节点应提前单独处理(或认为有一个必炸的 0 号节点与根相连)。

一定不炸的点即为第二问答案,中途记录,排序输出即可。

看代码吧,有详细注释

#include<iostream>
#include<cstdio>
#include<cstring> 
#include<algorithm>
#define N 21000
using namespace std;
long long n,head[N],Num=0;
long long c=0,f[N],g[N],boom[N],ans2[N],ans1;//f数组表示当前计算到点 i,且对于点 i 不炸,已经炸了的最少的城市去除了其下的所有边,g[i] 表示当前计算到点 i,且对于点 i 一定炸,已经炸了的最少的城市去除了其下的所有边。  
//boom[i]表示i这个点的炸还是不炸,或者炸不炸都行,boom[i]=0,表示不炸,boom[i]=1,表示炸不炸都行,boom[i]=2,表示炸, 
bool pass[N];
struct hh
{
    int next,to;
}a[100000];//链式前向星 
void add(int x,int y)//加边操作 
{
    Num++;
    a[Num].next=head[x];
    a[Num].to=y;
    head[x]=Num;
    er[x]++;
}

void dp(int x)//找最少需要炸掉的城市数量 
{
    long long i,j,k;
    for(i=head[x];i;i=a[i].next)//遍历子节点 
    {
        j=a[i].to;
        if(pass[j]==0)
        {
            pass[j]=1;
            dp(j);
            f[x]+=g[j];
            g[x]+=min(f[j],g[j]);
        }
    }
    g[x]++;
}
void find(int x)
{
    long long i,j,k;
    for(i=head[x];i;i=a[i].next)//遍历子节点 
    {
         j=a[i].to;
        if(!pass[j])
        {
            pass[j]=1;
            if(boom[x]==0)//父节点不炸,子节点一定炸 
            {
                boom[j]=2;
            }
            if(boom[x]==1)//父节点炸不炸都行 
            {
                if(f[j]>g[j])//炸更优 
                {
                    boom[j]=2;
                }
                else 
                {
                    boom[j]=1;//无法确定父节点这还是不炸,所以子节点为炸或不炸都行的状态 
                }
            }
            if(boom[x]==2)//父节点炸 
            {
                if(f[j]<g[j])//子节点不炸更优 
                {
                    boom[j]=0;
                    c++;
                    ans2[c]=j;
                }
                 if(f[j]==g[j])//无法确定 
                {
                    boom[j]=1;
                }
                if(f[j]>g[j])//子节点炸更优 
                {
                    boom[j]=2;
                }
            }
            find(j);//接着递归找答案 
        }
    }
}
int main()
{
    freopen("in.txt","r",stdin);
    memset(boom,0,sizeof(boom));
    memset(g,0,sizeof(g));
    memset(f,0,sizeof(f));
    cin>>n;
    for(int i=1;i<=n-1;i++)
    {
        int x,y;
        cin>>x>>y;
        add(x,y);//双向图嘛 
        add(y,x);//所以加两次咯 
    }
    pass[1]=1;
    dp(1);
    ans1=min(f[1],g[1]);

    memset(pass,0,sizeof(pass));
    memset(ans2,0,sizeof(ans2));
    if(f[1]<g[1])
    {
        boom[1]=0;//不炸根节点更好,所以加入答案2的数组 
        c++;
        ans2[c]=1;
    }
    if(f[1]>g[1])//炸会更优 
    {
        boom[1]=2;
    }
    if(f[1]==g[1])//不确定 
    {
        boom[1]=1;
    }
    pass[1]=1;
    find(1);
    sort(ans2+1,ans2+c+1);
    cout<<ans1<<endl;
    if(c>0)
    {
        for(int i=1;i<=c-1;i++)
    {
        cout<<ans2[i]<<" ";
    }
    cout<<ans2[c];
    }
    return 0;
}

总结:今天太浪,考试根本没写,还有3天集训结束,还是要认真!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值