2018.4.7dp模拟赛

A

天上掉馅饼本指天空中降落类似馅饼那样的即免费又好吃的食物,泛指在自然生活中会无缘无故的发生一些可以满足人们欲望的物质或财富上面的事情;现在也贬指那些坐享其成,成天不务实际的人在假想一些不可能发生的事情,等待出现奇迹,天上怎么可能会掉馅饼。

有一天, 小王同学正走在路上,忽然天上掉下大把大把的馅饼(哈哈哈。。。。)。这个只能说小王同学的人品太好,这馅饼就掉落在他身旁的10米范围内。所以小王同学马上去接馅饼,因为掉在地方的馅饼就不能吃了。他只能在这个10米范围内接馅饼。由于小王同学是一个非常优秀的Oier,但他不是一个优秀的运动员,所以他每秒钟只有在移动不超过一米的范围内接住坠落的馅饼。现在给这条小径如图标上坐标:

为了使问题简化,假设在接下来的一段时间里,馅饼都掉落在0-10这11个位置。开始时小王站在5这个位置上,因此在第一秒,他只能接到4,5,6这三个位置中其中一个位置上的馅饼。问小王最多可能接到多少个馅饼?(假设他的背包可以容纳无穷多个馅饼

这道题还是比较简单的,输入转化为矩阵做数字三角形即可。

想不到这个也可以按照时间排序, f[i][j] f [ i ] [ j ] 表示前i时间内第j个位置上最多能接到的馅饼数。直接转移

我使用struct存的,排序的时候mycup写错查了好久#滑稽 还罚了好多次时间。

#include<bits/stdc++.h>
using namespace std;
int f[100100][12];
int n , T;
struct question{
    int t , p;
}a[100100];
int read(){
    int sum = 0;;char c = getchar();
    while(c<'0'||c>'9') c = getchar(); 
    while(c>='0'&&c<='9') sum = sum * 10 + c - 48 , c = getchar();
    return sum;
}
bool mycup(question x, question y){
    return x.t < y.t || (x.t == y.t && x.p < y.p);
}
void clear(int x){
    f[x][0] = max(f[T][0] , f[T][1]);
    for(int i = 1;i <= 10;++i )
     f[x][i] = max( f[T][i] , max( f[T][i-1] ,f[T][i + 1]));
    return;
}
void init(){
    n = read();
    while(n){
        memset( f , -10 ,sizeof(f));
        f[0][5] = 0;
        for(int i = 1;i <= n;++i)
         a[i].p = read(),a[i].t = read();
        sort( a + 1 , a + n + 1,mycup);
        int ans = 0;
        T = 0;
        for(int i = 1; i <= n;++i){
            if(a[i].t != T)
             while(T<a[i].t)clear( T + 1 ) , T++;
            f[T][a[i].p]++;   
            ans = max(ans,f[T][a[i].p]);
        }
        printf("%d\n",ans);
        n = read();
    }
    return;
}
int main(){
    init();
    return 0;
} 

B

话说某个幸运的小伙伴X拿到了kevin女神送的蛋糕,然而他的吃法非常奇特,他独创了两种吃蛋糕的办法:一、一次吃一整个蛋糕;二、一次吃k个蛋糕。
那么,当蛋糕数量为x1到x2之间时,一共能有几种不同的吃法呢?
由于答案很大,输出结果mod 1000000007的值

这道题也非常的裸是吧。
f[i]=f[i1]+f[ik] f [ i ] = f [ i − 1 ] + f [ i − k ]
f[1] f [ 1 ] f[k] f [ k ] 提前处理好即可,非常的裸。

#include<bits/stdc++.h>
using namespace std;
int mod = 1e9 + 7;
int f[101000];
int read(){
    int sum = 0;;char c = getchar();
    while(c<'0'||c>'9') c = getchar(); 
    while(c>='0'&&c<='9') sum = sum * 10 + c - 48 , c = getchar();
    return sum;
}
void init(){
    int T = read() , k =read();
    f[0] = 1;
    for(int i = 1;i < k;++i)
     f[i] = 1;
    for(int i = k;i <=100000;++i)
     f[i] = (f[i-1] + f[i-k]) % mod;
    for(int i = 1;i <=100000;++i)
     f[i] = (f[i] + f[i-1]) % mod;
    for(int i = 1;i <= T;++i)
     {
        int x = read(), y = read();
        printf("%d\n",(f[y] - f[x-1] + mod) % mod);
     }
    return;
}
int main(){
    init();
    return 0;
}

C

有n个水果, 每个水果都有两个属性值ai表示美味程度, bi表示能量值, 现在要求选出一个或多个水果, 使得选出的水果的ai和与bi和的比例是k 问在这种清形可能出现的情况下ai的和最多是多少, 如果这样的情形不存在输出 -1

c就不是那么裸了。
要求 a[i]b[i]=k ∑ a [ i ] ∑ b [ i ] = k 时最大的 a[i] ∑ a [ i ]
我们可以把条件转换为 a[i]=kb[i] ∑ a [ i ] = k ∗ ∑ b [ i ]
a[i]kb[i] ∑ a [ i ] − k ∗ b [ i ]
那么就转化为裸的背包了(还是那么裸)
重量为 a[i]kb[i] a [ i ] − k ∗ b [ i ] 权值为 a[i] a [ i ]
因为有负数,而且数据很小,我们大可以设置两维, f[i][j] f [ i ] [ j ] 表示选前i个重量为j的答案。
这里得设置偏移量防止容量负数。

#include<bits/stdc++.h>
using namespace std;
int n ,k , N ;
int f[110][20100];
int w[110] , v[110];
int read(){
    int sum = 0;;char c = getchar();
    while(c<'0'||c>'9') c = getchar(); 
    while(c>='0'&&c<='9') sum = sum * 10 + c - 48 , c = getchar();
    return sum;
}
void init(){
    n = read();k = read();
    memset( f , -10 , sizeof(f));
    int N = n * 100;
    f[0][N] = 0;
    for(int i = 1;i <= n;++i)
     v[i] = read();
    for(int i = 1;i <= n;++i)
     w[i] = v[i] - k * read(); 
    for(int i = 1;i <= n;++i)
     for(int j = 2 * N;j >= max(w[i] , 0);--j)
       f[i][j] = max( f[i - 1][j] ,f[i-1][j - w[i]] + v[i]);
    if(f[n][N] <= 0) printf("-1");
     else printf("%d",f[n][N]);
    return;
}
int main(){
    init();
    return 0;
}

D

Once Petya read a problem about a bracket sequence. He gave it much thought but didn’t find a solution. Today you will face it.

有一个字符串 s. 这个字符串是一个完全匹配的括号序列. A correct bracket sequence is the sequence of opening (“(“) and closing (“)”) brackets, such that it is possible to obtain a correct mathematical expression from it, inserting numbers and operators between the brackets. For example, such sequences as “(())()” and “()” are correct bracket sequences and such sequences as “)()” and “(()” are not.

在这个完全匹配的括号序列里,每个括号都有一个和它匹配的括号 (an opening bracket corresponds to the matching closing bracket and vice versa). For example, in a bracket sequence shown of the figure below, the third bracket corresponds to the matching sixth one and the fifth bracket corresponds to the fourth one.

你现在可以给这个匹配的括号序列中的括号染色,且有三个要求:

每个括号只有三种情况,不上色,上红色,上蓝色.
每对括号必须只能给其中的一个上色,且必须给一个上色;For any pair of matching brackets exactly one of them is colored. In other words, for any bracket the following is true: either it or the matching bracket that corresponds to it is colored.
相邻的两个不能上同色,可以都不上色;No two neighboring colored brackets have the same color.
求满足条件的括号序列染色的方法数, 这个数值会比较大,只需要 mod 1000000007 (109 + 7).
Find the number of different ways to color the bracket sequence. The ways should meet the above-given conditions. Two ways of coloring are considered different if they differ in the color of at least one bracket. As the result can be quite large, print it modulo 1000000007 (109 + 7).

这道题就有点点难度了。

好在数据很小,那就区间dp吧
f[l][r][i][j] f [ l ] [ r ] [ i ] [ j ] 表示 L到R,左端点颜色为I,右端点颜色为J的方案数。
颜色我们这里0表示无色,1和2分别表示两种颜色。

首先我们可以循环赋值,但是这边时间复杂度会超。

考虑怎么改
我们可以用类似二分的办法去递归求解。
但是这里不可避免的会有一个括号左括号在递归的左边,右括号在递归的右边。
那么就会WA。

我们考虑这个dp的性质,既然括号不能被拆开,我们就找到左端点所匹配的右括号,以这右括号为中点递归两边,这样就不会有被拆开的括号了。

然后在考虑相邻两个合并的影响即可。

代码比较长。思路很清晰。

#include<bits/stdc++.h>
using namespace std;
#define ll long long
int f[800][800][3][3] , n ;
int st[800];
int top;
int linkk[800];
char c[800];
const int INF = 1e9 + 7;
int read(){
    int sum = 0;;char c = getchar();
    while(c<'0'||c>'9') c = getchar(); 
    while(c>='0'&&c<='9') sum = sum * 10 + c - 48 , c = getchar();
    return sum;
}
void dp(int l ,int r){
    if(l + 1 == r)  return;
    int mid = linkk[l];
    if(linkk[l] == r){
        dp( l + 1, r - 1);
        for(int i = 0;i <= 2;++i)
          for(int j = 0;j <= 2;++j){
            if(j != 1) f[l][r][0][1] = (f[l][r][0][1] + f[l + 1][r - 1][i][j]) % INF;
            if(i != 1) f[l][r][1][0] = (f[l][r][1][0] + f[l + 1][r - 1][i][j]) % INF;
            if(j != 2) f[l][r][0][2] = (f[l][r][0][2] + f[l + 1][r - 1][i][j]) % INF;
            if(i != 1) f[l][r][2][0] = (f[l][r][2][0] + f[l + 1][r - 1][i][j]) % INF;
          }
    }
    else{
        dp( l , mid );
        dp( mid + 1 , r );
        for(int i = 0;i <= 2;++i)
         for(int j = 0;j <= 2;++j)
          for(int x = 0;x <= 2;++x)
           if( x!=j || x==0)
           for(int y = 0;y <= 2;++y)
            f[l][r][i][y] =(( (1ll*f[l][mid][i][j] * f[mid + 1][r][x][y]) % INF) + f[l][r][i][y])%INF;
    }
    return;
}
void first(){
    for(int i = 1;i < n;++i)
     f[i][i + 1][0][1] = 1,
     f[i][i + 1][1][0] = 1,
     f[i][i + 1][2][0] = 1,
     f[i][i + 1][0][2] = 1;
        for(int i = 1;i < n;++i)
         if(linkk[i] != i+1)
           f[i][i + 1][2][1] = 1,
       f[i][i + 1][1][2] = 1;
    return;
}
void init(){
    scanf("%s",c); 
    int len = strlen(c);
    n = len;
    for(int x = 1;x <= len;++x){
        int i = x - 1;
        if(c[i] == '(') st[++top] = x;
         else linkk[st[top]] = x, linkk[x] = st[top] , top--;
    }
    return;
}
int main(){
    init();
    first();
    dp( 1 , n );
    long long ans = 0;
    for(int i = 0;i <= 2;++i)
     for(int j = 0;j <= 2;++j)
      ans = ( ans + f[1][n][i][j]) % INF;
    printf("%d",ans);
    return 0;
}

E

现有一个字符串 s = s1s2… s|s| of length |s|, 由小写字符组成. 现在有 q 次查询, 每次查询给两个整数 li, ri (1 ≤ li ≤ ri ≤ |s|). 每次查询你的程序要给出此字符串的子串 s[li… ri]有多少个回文串.

String s[l… r] = slsl + 1… sr (1 ≤ l ≤ r ≤ |s|) is a substring of string s = s1s2… s|s|.

字符串 t 叫 回文串的条件是, 如果这个字符串从左往右读与从右往左读是一样的. Formally, if t = t1t2… t|t| = t|t|t|t| - 1… t1.

既然是dp 好像能用的也只有区间dp。
预处理好 flag[i][j] f l a g [ i ] [ j ] 表示i到j是否是回文串。
处理的时候我们发现,如果 f[i][j] f [ i ] [ j ] 为真,且长度大于2,那么 f[i+1][j1] f [ i + 1 ] [ j − 1 ] 也为真。
那么我们只要n^2的预处理出来这个数组即可。

考虑转移,枚举长度,起点终点。
用容斥原理转移。
f[i][j]=f[i+1][j]+f[i][j1]f[i][j]+flag[i][j] f [ i ] [ j ] = f [ i + 1 ] [ j ] + f [ i ] [ j − 1 ] − f [ i ] [ j ] + f l a g [ i ] [ j ]

代码很短。

#include<bits/stdc++.h>
using namespace std;
int n , sum; 
char s[5100];
bool flag[5100][5100];
int f[5100][5100];
int read(){
    int sum = 0;;char c = getchar();
    while(c<'0'||c>'9') c = getchar(); 
    while(c>='0'&&c<='9') sum = sum * 10 + c - 48 , c = getchar();
    return sum;
}
void first(){
    for(int i = 1;i <= n;++i)
     flag[i][i] = true;
    for(int i = 1;i < n;++i)
     if(s[i] == s[i + 1]) 
      flag[i][i + 1] = true;
    for(int len = 3;len <= n;++len) 
     for(int i = 1;i <= n - len + 1;++i){
        int j = i + len - 1;
        if(flag[i + 1][j - 1]&&s[i]==s[j]) flag[i][j] = true;
     }
    return;
} 
void work(){
    for(int i = 1;i <= n;++i)
     f[i][i] = 1;
    for(int len = 2;len <= n;++len)
     for(int i = 1;i <= n - len + 1;++i){
        int j = i + len - 1;
        f[i][j] = f[i + 1][j] + f[i][j - 1] - f[i + 1][j - 1] + flag[i][j];
     } 
    int k = read();
    for(int i = 1;i <= k ;++i){
        int x = read(),y = read();
        printf("%d\n",f[x][y]); 
    }
    return;
}
int main()
{
    scanf("%s",s + 1);
    n = strlen(s + 1);
    first();
    work();
    return 0;
}   

F

给你一个长度为n的长字符串,“完美子串”既是它的前缀也是它的后缀,求“完美子串”的个数且统计这些子串的在长字符串中出现的次数

唯一没有A的题。
用到kmp的思想
同604

#include<bits/stdc++.h>
using namespace std;
int n , ans ;
char s[101000];
int next[101000];
int an[101000];
struct ans{
    int a , b;
}anss[101000];
int read()
{
    bool flag=true;
    int num=0;char c=getchar();
    for(;c<'0'||c>'9';c=getchar())if(c=='-') flag=false;
    for(;c>='0'&&c<='9';c=getchar())
    num=(num<<3)+(num<<1)+c-48;
    if(flag) return num;
      else return -num; 
}
void kmp()
{
    next[0]=-1;
    int j=-1;
    for(int i=1;i<n;++i)
     {
        while(j>=0&&s[j+1]!=s[i]) j=next[j];
        if(s[j+1]==s[i]) ++j;
        next[i]=j;
     }
    return;
}
int main()
{
    scanf("%s",s);
    n = strlen(s);
    kmp();
    int p = n - 1;
    for(int i = 0;i < n;++i) an[i] = 1;
    for(int i = n - 1;i > 0;--i) an[next[i]] += an[i];
    while(p != -1) {
        anss[++ans].a = p + 1,anss[ans].b = an[p];
        p = next[p] ;
    }
    printf("%d\n",ans);
    for(int i = ans;i >= 1;--i)
     printf("%d %d\n",anss[i].a,anss[i].b);
    return 0;
}

G

树是一个无环联通图. 现在给你一个 n 个点的树. 树的 重心 的定义为,如果把这个点去掉,剩余的每个联通分量的结点个数不会超过.

给定 n个点的树,现在你可以进行一次移边操作. 移边 指的是删除一条边 (不删除相应的点) 再增加一条边 (不增加新的点) 操作后还这个图还具有树的性质. 现在问,如果可以进行一次移边操作,每个点是否可以成为树的重心。 For each vertex you have to determine if it’s possible to make it centroid by performing no more than one edge replacement.

个人认为最难的一题。

显然对于初始状态。
某个点 i 要么本来就是中心,要么有且仅有一个子树大于n/2 (否则总点数会大于n)。
那么对于有一个子树大于n/2的情况,我们考虑怎么删边。
其实删边可以等价于将其中一棵子树移到另一个父亲下面。
同时我们会发现,对于一个点来说,移到其他地方都不如直接移到这个点下面来的优。
方便处理,我们只要找到这个子树中不超过n/2的最大的子树。判断这个子树的规模减去这个子树的子树的规模是否 <n/2 < n / 2 <script type="math/tex" id="MathJax-Element-701"> 假设用mx[i]表示以i为根 不超过n/2的最大的子树。
那么若 son[i]mx[i]<=n/2 s o n [ i ] − m x [ i ] <= n / 2 就满足。

这里我们得考虑一种特殊情况,如果这棵大于n/2的子树是当前点的父亲,那么直接这样做就不行了。我们得先判断他的父亲的mx[i]是否是在当前点的子树中。如果是那么显然我们就不能用mx[]来判断了。
假设我们有一个数组mx2[]存的是次大值。
且我们每次更新的时候都只用一整个子树,也就是和当前点连出去的点所形成的子树来更新答案。那么mx和mx2不会存在于同一棵子树中。
那就好处理了,我们再用id[]和id2[]表示最大值次大值是被连出去的哪个点更新的就可以判断了。

代码很长,思维也有点麻烦。

#include<bits/stdc++.h>
using namespace std;
#define ll long long
int n , k , linkk[401000] , t ;
int son[401000] , f[401000] , road[401000] , fat[401000] , Max[401000] ;
int id2[401000] , mx2[401000] , id1[401000] , mx1[401000];
int sum[401000];
int ans[401000];
bool flag;
struct node{
    int n , y;
}e[801000];
int read(){
    int sum = 0;;char c = getchar();
    while(c<'0'||c>'9') c = getchar(); 
    while(c>='0'&&c<='9') sum = sum * 10 + c - 48 , c = getchar();
    return sum;
}
void dfs(int x, int fa){
    for(int i = linkk[x];i;i = e[i].n)
     if(e[i].y != fa){
        int y = e[i].y;
        dfs( y , x );
        son[x] += son[y];
        if(son[y] > n / 2) sum[x]++;
          else if(son[y] > Max[x]) Max[x] = son[y]; 
        if(son[y] > f[x]) f[x] = son[y] , road[x] = y;
        Max[x] =max(Max[x] , Max[y]);
     }
    son[x]++;
    fat[x] = fa;
    if( n - son[x] > n / 2) sum[x]++;
  //   else 
 //   if(n - son[x] > Max[x]) Max[x] = n - son[x];
    if( n - son[x] > f[x]) f[x] = n - son[x] , road[x] = fa;
    if(son[x] <= n/2) Max[x] = max(Max[x],son[x]);
    return; 
}
void insert(int x , int  y){
    e[++t].y = y;e[t].n = linkk[x];linkk[x] = t;
    e[++t].y = x;e[t].n = linkk[y];linkk[y] = t;
    return;
}
void init(){
    n = read();
    for(int i = 1;i < n;++i){
        int x = read(), y = read();
        insert( x , y );
    }
    return;
}void dfs2(int x,int fa,int sf,int mf)
{
    mx1[x] = mx2[x] = -1;
    for(int i = linkk[x]; i ; i = e[i].n){
        int y = e[i].y;
        if(y == fa) continue;
        if( mx1[x] < Max[y]){
            mx2[x] = mx1[x];
            id2[x] = id1[x];
            mx1[x] = Max[y];
            id1[x] = y;
        }
        else if( mx2[x] < Max[y]){
            mx2[x] = Max[y];
            id2[x] = y;
        }
    }
    for(int i = linkk[x]; i ; i = e[i].n){
        int y = e[i].y;
        if(y == fa) continue;
        int fa_1 = 0,fa_2;
        if(n - son[y] <= n/2) fa_1 = n - son[y];
        if(id1[x] == y) fa_2 = mx2[x];
         else fa_2 = mx1[x];
        fa_2 = max(fa_2,mf);
       // if(x==1&&y == 4)printf("***%d %d %d %d\n",fa_1,fa_2);
        dfs2(y,x,son[x]-son[y]+sf,max(fa_1,fa_2));
    }
        if( mx1[x] < mf){
            mx2[x] = mx1[x];
            id2[x] = id1[x];
            mx1[x] = mf;
            id1[x] = fa;
        }
        else
        if( mx2[x] < mf){
            mx2[x] = mf;
            id2[x] = fa;
        }
    return;
}
void solve(){
    for(int i = 1;i <= n;++i){
        if(sum[i] > 1)printf("0 ");
        if(sum[i] == 0) printf("1 ");
        if(sum[i] == 1) {
            int ans;
            if( road[i] != fat[i] )
              ans = (son[road[i]] - Max[road[i]] <= n/2);
            else{         
            if(i == id1[road[i]]) ans = (n - son[ i ] - mx2[road[i]] <= n/2 );
              else
               ans = (n - son[i] - mx1[road[i]] <= n/2);
            } 
            printf("%d ",ans); 
        }
    }
    return;
}

H

给定n个城市,他们之间的连接方式为一棵树 ,由n-1条道路直接连接,这n-1条道路的长度都是相同的

现在有 2k 个不同的城市想与其他城市建立友好城市.

现在你的问题是,如何让这些城市完成匹配,或者叫友好城市配对后,这些友好城市之间的距离累加和最大。

h不是很难,既可以枚举每条边贪心,也可以从子树往上贪。都能过。

#include<bits/stdc++.h>
using namespace std;
#define ll long long
int n , k , linkk[201000] , t , now;
struct node{
    int n , y;
}e[401000];
int son[201000] , spe[201000] ;
long long ans;
bool flag[201000];
int read(){
    int sum = 0;;char c = getchar();
    while(c<'0'||c>'9') c = getchar(); 
    while(c>='0'&&c<='9') sum = sum * 10 + c - 48 , c = getchar();
    return sum;
}
void insert(int x , int  y){
    e[++t].y = y;e[t].n = linkk[x];linkk[x] = t;
    e[++t].y = x;e[t].n = linkk[y];linkk[y] = t;
    return;
}
void init(){
    n = read();k = read();
    for(int i = 1;i <= 2*k;++i)
     flag[read()] = true;
    for(int i = 1;i < n;++i){
        int x = read(), y = read();
        insert( x , y );
    }
    now = 2 * k;
    return;
}
void solve(int x ,int fa){
    for(int i = linkk[x];i;i = e[i].n)
     if(e[i].y != fa){
        int y = e[i].y;
        solve( y , x );
        spe[x] += spe[y];
     }
    if(flag[x]) spe[x]++;//统计
    while(spe[x]  >  now / 2){
        int tmp = (spe[x] + 1 - now / 2) / 2 * 2;
        spe[x] -= tmp;
        ans += tmp;
        now -= tmp;
    }
    if(flag[x]) spe[x] --;//特殊情况
    ans += spe[x] ;
    if(flag[x]) spe[x] ++;
    return;
}
int main(){
    init();
    solve( 1 , 0 );
    printf("%lld",ans);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值