2020牛客寒假算法基础集训营3(全解)

题目链接:https://ac.nowcoder.com/acm/contest/3004

这里出题人的题解很详细,有多种解法:https://ac.nowcoder.com/discuss/365306?type=101&order=0&pos=1&page=1

题目说明:

A.牛牛的DRB迷宫I      B.牛牛的DRB迷宫II       C.牛牛的数组越位      D.  牛牛与二叉树的数组存储

(DP|记忆化搜索)    (二进制)                       (模拟+STL)           (模拟)

E.牛牛的随机数          F.牛牛的Link Power I     G.牛牛的Link Power II    H.牛牛的k合因子数

(数位DP)               (前缀和)                       (线段树)                       (素数筛)

I.牛牛的汉诺塔          J.牛牛的宝可梦Go

(记忆化搜索)       (DP+最短路)

A.牛牛的DRB迷宫I 

题目大意:给你n*m的图(n,m<=50),问你从(1,1)到(n,m)有多少种走法,其中地图包含B,R,D(R只能向右走,D只能向下走,B都可),答案对1e9+7取模

示例

输入

5 5
RBBBR
BBBBB
BBBDB
BDBBB
RBBBB

输出

25

。。。一道格子DP,我们应该都做过只能向下和向右走的地图,$dp[i][j]+=dp[i][j-1]+dp[i-1][j]$,那么这题其实是一样的,不过我们也可以搜索,只不过为了防止TLE,我们可以记忆化一下。

DP的AC代码:

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;

const int mac=55;
const int mod=1e9+7;
int n,m;
char s[mac][mac];
ll dp[mac][mac];

int main(int argc, char const *argv[])
{
    int n,m;
    scanf ("%d%d",&n,&m);
    for (int i=1; i<=n; i++)
        scanf ("%s",s[i]+1);
    dp[n][m]=1;
    for (int i=n; i>=1; i--)
        for (int j=m; j>=1; j--){
            if (s[i][j]=='R') dp[i][j]=(dp[i][j]+dp[i][j+1])%mod;
            else if (s[i][j]=='D') dp[i][j]=(dp[i][j]+dp[i+1][j])%mod;
            else dp[i][j]=(dp[i][j]+dp[i][j+1]+dp[i+1][j])%mod;
        }
    printf("%lld\n",dp[1][1]);
    return 0;
}
View Code

记忆化搜索的AC代码:

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
const int mac=55;
const int mod=1e9+7;
#define ok(x,y,n,m) ((x>=1) && (x<=n) && (y>=1) && (y<=m))

char s[mac][mac];
ll dp[mac][mac],mk[mac][mac];
int vis[mac][mac],n,m;
int dx[]={0,1},dy[]={1,0};//向右,向下

ll dfs(int x,int y,int id)
{
    if (dp[x][y]) return dp[x][y];
    if (x==n && y==m) {dp[x][y]++; return dp[x][y];}
    int xx,yy;
    if (id==0 || id==1){
        xx=x+dx[id];yy=y+dy[id];
        if (vis[xx][yy] || !ok(xx,yy,n,m)) return 0;
        vis[xx][yy]=1;
        dp[x][y]=(dp[x][y]+dfs(xx,yy,mk[xx][yy]))%mod;
        vis[xx][yy]=0;
    } 
    else {
        for (int i=0; i<2; i++){
            xx=x+dx[i];yy=y+dy[i];
            if (vis[xx][yy] || !ok(xx,yy,n,m)) continue;
            vis[xx][yy]=1;
            dp[x][y]=(dp[x][y]+dfs(xx,yy,mk[xx][yy]))%mod;
            vis[xx][yy]=0;
        }
    }
    return dp[x][y];
}

int main(int argc, char const *argv[])
{
    scanf ("%d%d",&n,&m);
    for (int i=1; i<=n; i++)
        scanf("%s",s[i]+1);
    for (int i=1; i<=n; i++)
        for (int j=1; j<=m; j++)
            if (s[i][j]=='R') mk[i][j]=0;
            else if (s[i][j]=='D') mk[i][j]=1;
            else mk[i][j]=2;
    memset(dp,0,sizeof dp);
    printf("%lld\n",dfs(1,1,mk[1][1]));
    return 0;
}
View Code

B.牛牛的DRB迷宫II

题目大意:和A题题面反过来的,那种地图如果有n种走法,输出地图(大小在50*50)

示例

输入

25

输出

5 5
RBBBR
BBBBB
BBBDB
BDBBB
RBBBB

emmmm,这张地图其实可以表示任何一个数,两种走法和一种走法,这样可以安排组合也就是几个两种走法的,几个一种走法的,这样一定能构成n。

#include <bits/stdc++.h>
using namespace std;

char mp[55][55];

int main(int argc, char const *argv[])
{
    int n,m,k;
    n=32,m=30;
    scanf ("%d",&k);
    for (int i=1; i<=m; i++) mp[n][i]='R';
    for (int i=1; i<n; i++){//对角线的前一个到后一个先设为B,其余废弃
        for (int j=1; j<=m; j++){
            if (i==1) mp[1][j]=(j<=2)?'B':'R';
            else {
                if (j<i-1) mp[i][j]='D';
                else if (j<i+2) mp[i][j]='B';
                else mp[i][j]='R';
            }
        }
    }//此时mp[n][m]为2^(n-1)
    for (int i=1; i<=m; i++){//将k拆解为二进制数
        if (!(k&(1<<(i-1)))) mp[i+1][i]='R';//如果k在此位为0,那么就变B为R
    }
    printf("%d %d\n",n,m);
    for (int i=1; i<=n; i++)
        printf("%s\n",mp[i]+1);
    return 0;
}
View Code

C.牛牛的数组越位

题目大意:T组数据,给你一个二维数组[n][m](n*m<=2e7)。接下来p个操作,每次操作$x,y,val$如果$x,y$有越界行为,则输出整个数组,之后输出“Undefined Behaviour”,如果有非法行为,直接输出“Runtime error”,否则输出整个数组后输出“Accepted”。

示例

输入

4
2 4 8
-1 4 1
1 -3 2
2 -6 3
0 3 4
-1 8 5
-2 13 6
-100 406 7
100 -393 8
5 5 1
-1 8 1234
10 10 1
5 -51 1
1 1 1
0 0 7

输出

1 2 3 4
5 6 7 8
Undefined Behaviour
0 0 0 1234 0
0 0 0 0 0
0 0 0 0 0
0 0 0 0 0
0 0 0 0 0
Undefined Behaviour
Runtime error
7
Accepted

就是个纯粹的模拟题,只不过如何判断二维数组的大小不知道,而且清空太麻烦了,我们可以用map,这样就免去了清空数组的时间:

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;

#define ok(x,y,n,m) ((x>=0) && (x<n) && (y>=0) && (y<m))

int main(int argc, char const *argv[])
{
    int t;
    scanf ("%d",&t);
    while (t--){
        int n,m,pp;
        scanf ("%d%d%d",&n,&m,&pp);
        int re=0,ub=0;
        typedef pair<int,int>use;
        map<use,int>q;
        int lim=n*m;
        while (pp--){
            int x,y,val;
            scanf ("%d%d%d",&x,&y,&val);
            int use=m*x+y;
            if (use<0 || use>=lim) re=1;//注意有等于号
            if (!ok(x,y,n,m)) ub=1;
            int xx=use/m,yy=use%m;
            q[make_pair(xx,yy)]=val;
        }
        if (re) printf("Runtime error\n");
        else if (ub) {
            for (int i=0; i<n; i++)
                for (int j=0; j<m; j++){
                    printf("%d",q[make_pair(i,j)]);
                    printf("%c",j==(m-1)?'\n':' ');
                }
            printf("Undefined Behaviour\n");
        }
        else {
            for (int i=0; i<n; i++)
                for (int j=0; j<m; j++){
                    printf("%d",q[make_pair(i,j)]);
                    printf("%c",j==(m-1)?'\n':' ');
                }
            printf("Accepted\n");
        }
    }
    return 0;
}
View Code

D.牛牛与二叉树的数组存储

题目大意:给你一颗满二叉树(数组表示)(不存在的节点用-1代替)输出树的尺寸和根节点,并按照顺序打印每个节点的父亲节点、左孩子、右孩子

示例

输入

7
3 -1 2 -1 -1 1 4

输出

The size of the tree is 4
Node 3 is the root node of the tree
The father of node 1 is 2, the left child is -1, and the right child is -1
The father of node 2 is 3, the left child is 1, and the right child is 4
The father of node 3 is -1, the left child is -1, and the right child is 2
The father of node 4 is 2, the left child is -1, and the right child is -1

以下是AC代码:

#include <bits/stdc++.h>
using namespace std;
 
const int mac=1e5+10;
 
struct node
{
    int fa,l,r;
}ans[mac];
int a[mac],n;
 
int sonval(int rt)
{
    if (rt>n) return -1;
    return a[rt];
}
 
void build(int rt,int lim,int fa)
{
    if (rt>lim) return ;
    if (a[rt]==-1) return ;
    ans[a[rt]]=node{fa,sonval(rt<<1),sonval(rt<<1|1)};
    build(rt<<1,lim,a[rt]);build(rt<<1|1,lim,a[rt]);
}
 
int main(int argc, char const *argv[])
{
    int sz=0;
    scanf ("%d",&n);
    for (int i=1; i<=n; i++){
        scanf("%d",&a[i]);
        if (a[i]!=-1) sz++;
        ans[i].fa=ans[i].l=ans[i].r=-1;
    }
    build(1,n,-1);
    printf("The size of the tree is %d\n",sz);
    printf("Node %d is the root node of the tree\n",a[1]);
    for (int i=1; i<=sz; i++){
        printf("The father of node %d is %d, ",i,ans[i].fa);
        printf("the left child is %d, ",ans[i].l);
        printf("and the right child is %d\n",ans[i].r);
    }
    return 0;
}
View Code

E.牛牛的随机数  

题目大意:T组数据(<=1e5),从区间$[l_{1},r_{1}]$和$l_{2},r_{2}$($l_{1},r_{1},l_{2},r_{2}<=1e18$)中分别取一个数$a,b$,问你$a\bigoplus b$的数学期望,并对1e9+7取模

示例

输入

2
3 5 7 8
1 3 3 5

输出

500000011
222222228

没办法,先看看暴力吧,$\frac{\sum_{x=l_{1}}^{r_{1}}\sum_{y=l_{2}}^{r_{2}}x\bigoplus y}{(r_{1}-l_{1}+1)\times (r_{2}-l_{2}+1)}$

然后就是跑数位DP了,出题人说的明白了,我就不多说了,一些小细节打了注释

以下是AC代码:

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
const ll mod=1e9+7;
ll a[105],b[105],na[105],nb[105];
struct node
{
    ll s,ss;
};

node dp[105];
bool vis[105];

node dfs(int pos,bool limt1,bool limt2)//x,y的第pos位限制
{
    if(pos==-1) return node{0,1};
    if(!limt1 && !limt2 && vis[pos]) return dp[pos];//pos位如果没有限制
    if(!limt1 && limt2)//y没有限制
       return node{(((1ll<<pos)%mod)*((2ll<<pos)%mod-1)%mod)*((nb[pos]+1)%mod)%mod,(((nb[pos]+1)%mod)*((2ll<<pos)%mod))%mod};
    if(limt1 && !limt2)
       return node{(((1ll<<pos)%mod)*((2ll<<pos)%mod-1)%mod)*((na[pos]+1)%mod)%mod,(((na[pos]+1)%mod)*((2ll<<pos)%mod))%mod};
    int up1=limt1?a[pos]:1;//如果没有限制,那你们此位的二进制数位最大1
    int up2=limt2?b[pos]:1;
    node temp{0,0};
    for (int i=0; i<=up1; i++)
        for (int j=0; j<=up2; j++){
            node sm=dfs(pos-1,limt1 && i==a[pos],limt2 && j==b[pos]);
            if (i^j){
                temp.s=(temp.s+(1ll<<pos)%mod*sm.ss%mod+sm.s)%mod;
                temp.ss=(temp.ss+sm.ss)%mod;
            }
            else{
                temp.s=(temp.s+sm.s)%mod;
                temp.ss=(temp.ss+sm.ss)%mod;
            }
        }
    if(!limt1 && !limt2){
        vis[pos]=true;
        dp[pos]=temp;
    }
    return temp;
}
ll solve(long long x,long long y)
{
    memset(na,0,sizeof(na));
    memset(nb,0,sizeof(nb));
    memset(a,0,sizeof(a));
    memset(b,0,sizeof(b));
    int len1=0,len2=0;
    while(x){//二进制下的每一位
        a[len1++]=x&1;
        x>>=1;
    }
    while(y){
        b[len2++]=y&1;
        y>>=1;
    }
    int len=max(len1,len2)-1;
    for(int i=0;i<=len;++i){
        x+=a[i]<<i;
        na[i]=x;
        y+=b[i]<<i;
        nb[i]=y;
    }
    return dfs(len,true,true).s;
}
ll qick(ll x,ll y)
{
    ll ans=1;
    while(y){
        if(y&1) ans=(x*ans)%mod;
        x=(x*x)%mod;
        y>>=1;
    }
    return ans;
}
int main()
{
    int t;
    memset(vis,false,sizeof(vis));
    scanf("%d",&t);
    while(t--){
        ll l1,l2,r1,r2;
        scanf("%lld%lld%lld%lld",&l1,&r1,&l2,&r2);
        ll ans=0;
        ans+=solve(r1,r2);
        ans-=solve(r1,l2-1);
        ans-=solve(l1-1,r2);
        ans+=solve(l1-1,l2-1);
        ans=(ans%mod+mod)%mod;
        ans=ans*qick(((r1-l1+1)%mod)*((r2-l2+1)%mod)%mod,mod-2)%mod;
        printf("%lld\n",ans);
    }
    return 0;
}
View Code

F. 牛牛的Link Power I 

题目大意:给你一个01串,dis(u,v)为数组上u到v的距离,(u,v)和(v,u)被认为是同一对。求所有字符1的dis的和,答案对1e9+7取模

示例

输入

3
101

输出

2

记录一下1的个数和每个1的位置,然后记录一下所有1位置的和,第一位置对答案的贡献就是:$(a_{2}-a_{1})+(a_{3}-a_{1})+\cdots +(a_{n}-a_{1})=s_{n}-a_{1}-(n-1)*a_{1}$

由于是无序对,所以,每次s都会减去当前的位置值。

以下是AC代码:

#include <bits/stdc++.h>
using namespace std;
 
const int mac=1e5+10;
const int mod=1e9+7;
typedef long long ll;
 
char s[mac];
vector<int>g;
 
int main(int argc, char const *argv[])
{
    int n;
    scanf ("%d",&n);
    scanf ("%s",s+1);
    int nb=0;
    for (int i=1; i<=n; i++)
        if (s[i]=='1') g.push_back(i);
    ll sum=0;
    for (auto x:g) sum+=x;
    ll ans=0;
    int len=g.size();
    for (auto x:g){
        ans=(ans+(sum-x)%mod-1ll*x*(len-1)%mod+mod)%mod;
        sum-=x;len--;
    }
    printf("%lld\n",ans);
    return 0;
}
View Code

G.牛牛的Link Power II    

题目大意:和上面的I差不多,只不过多了个修改操作,q,pos,q=1即将pos位置的0改成1,q=2,将pos位置1改为0

示例

输入

5
00001
7
1 1
2 5
2 1
1 2
1 4
1 3
1 1

输出

0
4
0
0
0
2
4
10

和上面差不多的思路,维护一下pos前面和后面的数量(num)和位置和(sum),那么新加一个点对前面的贡献也就是$ans+pos*num-sum$,对后面的贡献就是$ans+sum-pos*num$,这个可以用线段树维护

以下是AC代码:

#include <bits/stdc++.h>
using namespace std;

#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
const int mac=1e5+10;
const int mod=1e9+7;
typedef long long ll;

struct node
{
    ll nb,val;
}tree[mac<<2];
char s[mac];

void build(int l,int r,int rt)
{
    tree[rt].nb=tree[rt].val=0;
    if (l==r) return;
    int mid=(l+r)>>1;
    build(lson);build(rson);
}

void update(int l,int r,int rt,int pos,int mk,int val)
{
    if (l==r) {
        tree[rt].nb=mk; 
        tree[rt].val=val;
        return;
    }
    int mid=(l+r)>>1;
    if (pos<=mid) update(lson,pos,mk,val);
    else update(rson,pos,mk,val);
    tree[rt].nb=tree[rt<<1].nb+tree[rt<<1|1].nb;
    tree[rt].val=tree[rt<<1].val+tree[rt<<1|1].val;
}

node solve(node a,node b)
{
    return node{a.nb+b.nb,a.val+b.val};
}

node query(int l,int r,int rt,int L,int R)
{
    node ans={0,0};
    if (l>=L && r<=R) return tree[rt];
    int mid=(l+r)>>1;
    if (L<=mid) ans=solve(ans,query(lson,L,R));
    if (R>mid) ans=solve(ans,query(rson,L,R));
    return ans;
}

int main(int argc, char const *argv[])
{
    int n;
    scanf ("%d",&n);
    build(1,n,1);
    scanf ("%s",s+1);
    ll sum=0;
    for (int i=1; i<=n; i++)
        if (s[i]=='1') update(1,n,1,i,1,i);
    int m;
    scanf ("%d",&m);
    for (int i=1; i<=n; i++){
        if (s[i]=='1'){
            node p=query(1,n,1,i+1,n);
            sum=(sum+p.val-p.nb*i%mod+mod)%mod;
        }
    }
    printf("%lld\n",sum);
    while (m--){
        int q,pos;
        scanf ("%d%d",&q,&pos);
        if (q==1){
            node p=query(1,n,1,1,pos);
            sum=(sum+(pos*p.nb%mod)-p.val+mod)%mod;
            p=query(1,n,1,pos,n);
            sum=(sum+p.val-(pos*p.nb%mod)+mod)%mod;
            update(1,n,1,pos,1,pos);
        }
        else {
            node p;
            if (pos!=1){
                p=query(1,n,1,1,pos-1);
                sum=(sum-(((pos*p.nb%mod)-p.val+mod)%mod)+mod)%mod;
            } 
            if (pos!=n){
                p=query(1,n,1,pos+1,n);
                sum=(sum-((p.val-(pos*p.nb%mod)+mod)%mod)+mod)%mod;
            }
            update(1,n,1,pos,0,0);
        }
        printf("%lld\n",sum);
    }
    return 0;
}
View Code

H.牛牛的k合因子数

题目大意:输出1~n(1e5)中给定k的情况下k合因子数(有k个因子为合数)的数目。m次询问

示例

输入

10 5
1
2
3
4
5

输出

4
1
0
0
0

埃式筛筛出质数,再对1到n中的所有数用$\sqrt{n}$的复杂度求每个数的因子并判断是否为质数就好了。

以下是AC代码:

#include <bits/stdc++.h>
using namespace std;
 
const int mac=1e5+10;
 
int disprime[mac],vis[mac],ans[mac];
 
int solve(int x)//判断有几个合数因子
{
    int ans=0;
    int p=sqrt(x);
    if (p*p==x)
        if (vis[p]) ans++;
    for (int i=1; i<=p; i++){
        if (i*i==x) continue;
        if ((x%i)==0) {
            int u=i,v=x/i;
            if (vis[u]) ans++;
            if (vis[v]) ans++;
        }
    }
    return ans;
}
 
int main(int argc, char const *argv[])
{
    int n,m;
    scanf ("%d%d",&n,&m);
    int p=sqrt(n);
    for (int i=2; i<=p; i++)
        if (!vis[i])
            for (int j=i*i; j<=n; j+=i)
                vis[j]=1;
    for (int i=2; i<=n; i++){
        disprime[i]=solve(i);
        ans[disprime[i]]++;
    }
    while (m--){
        int x;
        scanf("%d",&x);
        printf("%d\n",ans[x]);
    }
    return 0;
}
View Code

I.牛牛的汉诺塔 

题目大意:给你n个盘子,输出移动次数(A-B,A-C,B-A,B-C,C-A,C-B)以及所有的次数:

示例

输入

3

输出

A->B:1
A->C:3
B->A:1
B->C:1
C->A:0
C->B:1
SUM:7  

方法一:打表找规律。。。。嘿嘿嘿,这个公式可不好推,不建议,不过它跑得非常快。。。时间复杂度O(1)

规律:

A->B:0,1,1,4,4,15,15,58,58

A->C:1,1,3,3,9,9,31,31,117,117

B->A:0,0,1,1,6,6,27,27,

B->C:0,1,1,4,4,15,15,58,58

C->A:0,2,2,12,12,54,54,224,224

C->B:0,0,1,1,6,6,27,27

以下是AC代码:

#include <bits/stdc++.h>
using namespace std;
 
typedef long long ll;
 
ll qick(ll a,ll b)
{
    ll ans=1;
    while (b){
        if (b&1) ans*=a;
        a*=a;
        b>>=1;
    }
    return ans;
}
 
int main(int argc, char const *argv[])
{
    int n;
    scanf ("%d",&n);
    ll sum=1ll<<n;
    sum--;
 
    printf("A->B:");
    ll nb=(n-1)/2;
    if ((n-1)%2==0) nb--;
    if (n==1) printf("0\n");
    else printf("%lld\n",(3*nb+1+qick(2,2*nb+3))/9);
 
    printf("A->C:");
    nb=n/2;
    if (n%2==0) nb--;
    printf("%lld\n",(qick(4,nb+1)+6*nb+5)/9);
 
    printf("B->A:");
    nb=n/2;
    if (n%2==0) nb--;
    printf("%lld\n",(qick(4,nb+1)-3*nb-4)/9);
 
    printf("B->C:");
    nb=(n-1)/2;
    if ((n-1)%2==0) nb--;
    if (n==1) printf("0\n");
    else printf("%lld\n",(3*nb+1+qick(2,2*nb+3))/9);
 
    printf("C->A:");
    nb=(n-1)/2;
    if ((n-1)%2==0) nb--;
    printf("%lld\n",2*(qick(4,nb+1)-3*nb-4)/9);
 
    printf("C->B:");
    nb=n/2;
    if (n%2==0) nb--;
    printf("%lld\n",(qick(4,nb+1)-3*nb-4)/9);
    printf("SUM:%lld\n",sum);
    return 0;
}
View Code

方法二:暴搜加个记忆化就完事了

以下是AC代码:

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;

struct node
{
    ll val[6];
    node(){memset(val,0,sizeof val);}
};
node dp[4][4][4][100];
int vis[4][4][4][100];

void solve(int x,int y,node &a)
{
    if(x==0&&y==1) a.val[0]++;
    else if(x==0&&y==2) a.val[1]++;
    else if(x==1&&y==0) a.val[2]++;
    else if(x==1&&y==2) a.val[3]++;
    else if(x==2&&y==0) a.val[4]++;
    else if(x==2&&y==1) a.val[5]++;
}

node deal(node a,node b)
{
    node ans;
    for (int i=0; i<6; i++)
        ans.val[i]=a.val[i]+b.val[i];
    return ans;
}

node dfs(int n,int a,int b,int c)
{
    if (vis[a][b][c][n]) return dp[a][b][c][n];
    if (n==1){
        solve(a,c,dp[a][b][c][n]);
        vis[a][b][c][n]=1;
        return dp[a][b][c][n];
    }
    node tmp;
    tmp=deal(tmp,dfs(n-1,a,c,b));
    solve(a,c,tmp);
    tmp=deal(tmp,dfs(n-1,b,a,c));
    vis[a][b][c][n]=1;
    return dp[a][b][c][n]=tmp;
}

int main(int argc, char const *argv[])
{
    int n;
    scanf("%d",&n);
    node ans=dfs(n,0,1,2);
    printf("A->B:%lld\n",ans.val[0]);
    printf("A->C:%lld\n",ans.val[1]);
    printf("B->A:%lld\n",ans.val[2]);
    printf("B->C:%lld\n",ans.val[3]);
    printf("C->A:%lld\n",ans.val[4]);
    printf("C->B:%lld\n",ans.val[5]);
    printf("SUM:%lld\n",(1ll<<n)-1);
    return 0;
}
View Code

J.牛牛的宝可梦Go

题目大意:n个城市(<=200),m条公路(<=10000)每条长度为1,有k个宠物,(<=1e5)将会在时间$t$,地点$pos$出现,其攻击力为$val$请问他能得到的最大攻击为多少?

示例

输入

3 2
1 2
2 3
3
1 1 5
2 3 10
3 2 1

输出

   11

        我们可以先考虑暴力,枚举每一个宠物,那么第i个宠物肯定是由前i-1个宠物转移过来的,也就是说$dp[i]=max(dp[i],dp[j]+val)$那么复杂度就是$O(K^{2})$然后就直接爆表了。但是我们可以知道的是只有200个城市,那么也就是说一次最多往前拓展200个,那么200个之后的我们直接用前缀最大值维护就好了。

以下是AC代码:

#include <bits/stdc++.h>
using namespace std;

const int mac=1e5+10;
typedef long long ll;
const ll inf=1e18;

ll dis[250][250];
ll dp[mac];
struct node
{
    int t,pos;
    ll val;
    bool operator <(const node &a)const{
        return t<a.t;
    }
}a[mac];

int main(int argc, char const *argv[])
{
    int n,m;
    scanf ("%d%d",&n,&m);
    memset(dis,0x3f,sizeof dis);
    for (int i=1; i<=n; i++) dis[i][i]=0;
    for (int i=1; i<=m; i++){
        int u,v;
        scanf("%d%d",&u,&v);
        dis[u][v]=dis[v][u]=1;
    }
    int q;
    scanf("%d",&q);
    for (int i=1; i<=q; i++){
        int t,p,val;
        scanf("%d%d%d",&t,&p,&val);
        a[i]=node{t,p,val};
    }
    for (int k=1; k<=n; k++)
        for (int i=1; i<=n; i++)
            for (int j=1; j<=n; j++)
                dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
    sort(a+1,a+1+q);
    for (int i=1; i<=q; i++) dp[i]=-inf;
    a[0].t=0;a[0].pos=1;
    ll tmp=0;
    ll ans=0;
    for (int i=1; i<=q; i++){
        if (i>200) {
            tmp=max(tmp,dp[i-200]);
            dp[i]=a[i].val+tmp;
        }
        for (int j=1; j<=min(i,200); j++){//[i-200,i]
            if (a[i].t-a[i-j].t>=dis[a[i].pos][a[i-j].pos])
                dp[i]=max(dp[i],dp[i-j]+a[i].val);
        }
        ans=max(ans,dp[i]);
    }
    printf("%lld\n",ans);
    return 0;
}
View Code
posted @ 2020-02-09 22:09  lonely_wind  阅读(255)  评论(0编辑  收藏  举报