牛客算法周周练16全题解

A—红包期望

题目描述
众所周知,在过年的时候每家每户都会发红包,现在clccle和qn在一起抢*信红包,但是她们觉得这样没有意思,便开始了计算,如果当clccle和sqn为第k个抢红包的人时候,所抢到红包金额的期望是多少?(红包的大小在[0,2n/m]中均匀随机,特别的当红包的大小小于2n/m时,最后剩下的金额会被包入最后一个红包中)

输入描述:
第一行,三个整数,m,n,T分别表示有一个红包可以被m个人领取,而且红包的总金额是n,接下来有T次询问
接下来T行,每行一个整数k,表示clccle和qn抢红包的时候是第几位
输出描述:
共T行
每行一个整数,表示clccle和qn所得到的红包大小的期望

示例1
输入
10 100 3
10
15
16
输出
10
0
0

说明
请自行证明(才不是因为窝证明之后你们就能直接出正解了呢qwq)

备注:
对于全部数据
1<=m<=n<=1e^18,1<=T<=50000

#include 
using namespace std;
typedef long long ll;

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);

    ll n,m,T;
    cin>>m>>n>>T;
    while(T--) {
        ll k;
        cin>>k;
        if(k>m)  cout<<0<<endl;
        else  cout<<n/m<<endl;
    }
    return 0;
}

B—死肥宅的冲分计划

题目描述
国庆十天假,死肥宅选择回家休息,休息期间死肥宅开始了他的召唤师峡谷冲分之旅,冲分之前死肥宅向其他队员打赌十天内必上王者,已知死肥宅初始段位为黄金,因为死肥宅是游戏鬼才,所以他可以控制每天的段位变化不会超过一个大段,每天都会有队员查看下死肥宅的段位,如果死肥宅上了一个大段那么就会在小本本上记一个1,如果大的段位没有变化那么就会在小本本上记一个0,如果掉了一个大段,那么就记一个7,但是存在一些特殊情况使得队员记录的数字代表的意义会发生一些改变(详情可看备注)十天后,我们可以得到一个10个数字的序列,试着根据这串序列算出死肥宅是否上了王者,如果死肥宅成功的话那么请输出“666”,否则请输出“777"。

输入描述:
多组输入,每行输入10个数字,只包含1,0,7,三个数字。
输出描述:
10天后如果死肥宅的段位达到王者,输出"666";否则输出"777"。

示例1
输入
7 7 7 7 7 7 7 7 7 7
1 1 1 1 1 1 1 1 1 1
输出
777
666

备注:
段位晋级规则为:
黄铜 -> 白银 -> 黄金 -> 白金 -> 钻石 -> 大师 -> 王者
特殊状况:
如果降到黄铜,且下一天仍然没有升段,因为黄铜下面没有其他段位,那么记录信息的队员会在7和0中随机记录一个,7和0都代表当前段位没有发生变化。
如果升到王者,且下一天仍然没有掉段,因为王者之上没有其他段位,那么记录信息的队员会在0和1中随机记录一个,1和0都代表当前段位没有发生变化。

#include 
using namespace std;

int main()
{
    int f[15];
    int m;
    while (~scanf("%d",&m))
    {
        if(m!=1 && m!=0 && m!=7)    break;
        
        f[0]=m;
        for(int i=1; i<10; i++)    scanf("%d",&f[i]);
        
        int num=10;
        for (int i=0; i<10; i++)
            {
                if (f[i]==1 && num!=14)    ++num;
                else if (f[i]==7 && num!=8)     --num;
            }
            if (num==14)    printf("666\n");
            else    printf("777\n");
    }
    return 0;
}

C—树链博弈

题目描述
给定一棵 n 个点的树,其中 1 号结点是根,每个结点要么是黑色要么是白色
现在小 Bo 和小 Biao 要进行博弈,他们两轮流操作,每次选择一个黑色的结点将它变白,之后可以选择任意多个(可以不选)该点的祖先(不包含自己),然后将这些点的颜色翻转,不能进行操作的人输
由于小 Bo 猜拳经常输给小 Biao,他想在这个游戏上扳回一城,现在他想问你给定了一个初始局面,是先手必胜还是后手必胜

输入描述:
第一行一个正整数 n
第二行 n 个整数 w1…wn,wi∈ {0,1},wi=1 表示第 i 个结点一开始是黑点,否则是白点
接下来 n-1 行,每行两个正整数 u,v 表示一条树边 (u,v)
输出描述:
如果先手必胜,输出First ,否则输出Second

示例1
输入
2
1 0
1 2
输出
First

备注:
1≤ n≤ 1000

先手存在一个必败态,就是当每一层的黑色结点数都为偶数时,先手必败

// 博弈论(sg函数)
#include 
using namespace std;
const int maxn = 1010;

struct edge {
    int to,nxt;
}e[maxn*2];

int head[maxn],w[maxn],depth[maxn],b[maxn],cnt,n;
 
void addEdge(int u,int v)
{
    e[++cnt].to = v;
    e[cnt].nxt = head[u];
    head[u] = cnt;
}
 
void dfs(int u,int fa)
{
    depth[u] = depth[fa]+1;
    if(w[u]) b[depth[u]] ++;
    for(int i=head[u];i;i=e[i].nxt)
    {
        int v = e[i].to;
        if(v==fa) continue;
        dfs(v,u);
    }
}
 
int main()
{
    bool flag = true;
    scanf("%d",&n);
    for(int i=1;i<=n;++i)    scanf("%d",&w[i]);
    
    for(int i=1;i<n;++i)
    {
        int u,v;
        scanf("%d %d",&u,&v);
        addEdge(u,v);
        addEdge(v,u);
    }
    
    dfs(1,0);
    for(int i=1;i<=n;++i)
    {
        if(b[depth[i]]%2)
        {
            flag = false;
            break;
        }
    }
    
    if(flag)  printf("Second\n");
    else  printf("First\n");
    return 0;
}

D—Rinne Loves Dynamic Graph

牛客算法周周练16全题解_第1张图片
输入描述:
第一行两个正整数 N,M,表示这个动态图的点数和边数。
接下来 M 行,每行三个正整数 u,v,w,表示存在一条连接点 u,v 的无向边,且初始权值为 w。
输出描述:
如果能到达的话,输出边权绝对值之和最小的答案,保留三位小数。
否则请输出 -1。

示例1
输入
3 3
1 2 2
2 3 2
3 1 3
输出
3.000
牛客算法周周练16全题解_第2张图片

所谓动态图,其实就是你走到这个点所对应的步数不同,图的边权所对应的值也随之发生改变。我们仔细观察这个函数,手动推导几项之后发现,该函数是一个周期变化的函数,并且周期为3。很明显本题就被转化成为了一个分层图最短路的模型,分成三层,分别对应到达该点步数%3的情况。并且,因为本题的边权都取绝对值,所以最好用Dijkstra

// 分层图最短路
#include 
using namespace std;
const int maxn = 1e6 + 100;
const int inf = 0x3f3f3f3f;

double getdis(int x, int t)
{
    if(t==0)    return abs(1.0 * x);
    else if(t==1)    return abs(1.0 / (1.0-x));
    else    return abs(1.0 - 1.0/x);
}

struct node1{
    int to, next, w;
} e[maxn];
int head[maxn];
int tot;

void add(int x, int y, int w)
{
    e[++tot].to = y;
    e[tot].w = w;
    e[tot].next = head[x];
    head[x] = tot;
}

double dis[maxn][5];
struct node
{
    int id, t;
    double dis;
    bool operator<(const node &b) const {
        return dis > b.dis;
    }
};

priority_queue<node> q;
bool vis[maxn][5];
int n, m;

void dijkstra()
{
    for (int i=1; i<=n; ++i)
        dis[i][0] = dis[i][1] = dis[i][2] = inf;
    dis[1][0] = 0;
    node t;
    t.id = 1;
    t.t = 0;
    t.dis = dis[1][0];
    q.push(t);
    while (!q.empty())
    {
        int u = q.top().id;
        int t = q.top().t;
        q.pop();
        if (vis[u][t])
            continue;
        vis[u][t] = 1;
        for (int i=head[u]; i; i=e[i].next)
        {
            int v = e[i].to;
            int t2 = (t+1) % 3;
            double v2 = getdis(e[i].w, t);
            //if (vis[v][t2])
              //  continue;
            if (dis[v][t2] > dis[u][t]+v2)
            {
                dis[v][t2] = dis[u][t]+v2;
                node ne;
                ne.id = v;
                ne.t = t2;
                ne.dis = dis[v][t2];
                q.push(ne);
            }
        }
    }
}

int main()
{
    scanf("%d%d", &n, &m);
    while (m--)
    {
        int a, b, c;
        scanf("%d%d%d", &a, &b, &c);
        add(a, b, c), add(b, a, c);
    }
    dijkstra();
    double ans = min(dis[n][0], dis[n][1]);
    ans = min(ans, dis[n][2]);
    if(ans==inf)    puts("-1");
    else    printf("%.3lf\n", ans);
    return 0;
}

E—1408

题目描述
小m曾经给小t和小s讲过一个奇怪的故事。这个故事叫做1408。故事的大体内容如下。
主人公迈克·安瑟林(约翰·库萨克饰)是一个恐怖小说家。将装神弄鬼作为本职工作的迈克,平日里却是个彻头彻尾的无神论者。为了完成自己的新书,迈克决定找一家“闹鬼”
的房间住上十天,而胆大包天的他最终选择了传闻中最阴森恐怖的海豚酒店的1408号房间。
酒店经理杰拉尔德·奥林(塞缪尔·杰克逊饰)拿出在那个房间中历年来数十起离奇死亡事件的照片,来力劝迈克不要冒险,但迈克满不在乎,坚持着自己的选择。而在搬进1408号房间后,一件件离奇的超自然现象让迈克彻底改变了自己的鬼神人生观。大量恐怖惊悚的真实体验为迈克的新书着实积累了素材,但问题的关键是,他必须得先活着走出那个房间。
以上写的只是简介,实际这个故事细思极恐。而由于小s当时住的宾馆是14楼,所以他有一点点慌。于是回房间之后根小b玩起了一个游戏。
游戏是这样的,小s有n个白球和n个黑球,白球和黑球的编号分别1~n排序。给你2*n行信息,每行第一个是一个字符,B表示它是黑球,W表示它是白球,后面的一个数字表示这个球在它自己的颜色中的编号。每一次可以交换相邻两个球的位置。问北神最少移动几次可以使得对于所有白球,序号是按顺序排列的且所有黑球的序号也是按顺序排列的。
小b觉得这个问题很有意思,所以也请你来做做。

输入描述:
输入一个数n,之后给你2*n行信息,信息内容见题面。
输出描述:
输出最小的交换次数
示例1
输入
3
B 1
W 2
B 3
W 1
W 3
B 2
输出
4

备注:
对于10%的数据n<=10
对于100%的数据n<=2000

// 线段树+DP
#include 
using namespace std;

const int maxn = 4e3+100;
int n,wnum,bnum;
char s[10];
int f[maxn][maxn];//前i个,白色前j个
int id[maxn],col[maxn];
int wpos[maxn],bpos[maxn];
int wcnt[maxn][maxn],bcnt[maxn][maxn];

inline int getw(int l,int r,int x){return wcnt[r][x] - wcnt[l-1][x];}
inline int getb(int l,int r,int x){return bcnt[r][x] - bcnt[l-1][x];}

int main()
{
    scanf("%d", &n);
    for(int i=1; i<=n*2; ++i)
    {
        scanf("%s", s+1), scanf("%d", id+i);
        col[i] = (s[1]=='B');
        if(col[i])  bpos[id[i]] = i;
        else  wpos[id[i]] = i;
    }
    
    for(int i=1; i<=2*n; ++i)
    {
        if(col[i]==0) 
		    for(int j=id[i]; j<=n; ++j)  wcnt[i][j]++;
        else 
		    for(int j=id[i]; j<=n; ++j)  bcnt[i][j]++;
		    
        for(int j=1; j<=n; ++j) wcnt[i][j] += wcnt[i-1][j], bcnt[i][j] += bcnt[i-1][j];
    }
    
    memset(f, 0x3f, sizeof(f));
    f[0][0] = 0;
    for(int i=1; i<=2*n; ++i)
    {
        for(int j=0; j<=n&&j<=i; ++j)
        {
            int bl=i-j;
            if(j<=i-1)
            {
                int bp = bpos[bl];
                f[i][j] = f[i-1][j] + bp-i + getw(bp+1, 2*n, j) + getb(bp+1, 2*n, bl-1);
            }
            if(j)
            {
                int wp = wpos[j];
                f[i][j] = std::min(f[i][j], f[i-1][j-1] + wp-i + getw(wp+1, 2*n, j-1) + getb(wp+1, 2*n, bl));
            }
        }
    }
    printf("%d\n",f[2*n][n]);
    return 0;
}

你可能感兴趣的:(图论,博弈论,DP)