20260519紫题训练

P3163 [CQOI2014] 危桥

题意

给出一个 nnn 个点的无向图,其中某些边只能经过两次

现有四个点 a1,a2,b1,b2a1,a2,b1,b2a1,a2,b1,b2 ,问能否从 a1a1a1 到 a2a2a2 2×an2\times an2×an 次,从 b1b1b1 到 b2b2b2 2×bn2\times bn2×bn 次

其中 n≤50n\leq 50n≤50

题解

考虑直接跑最大流,但这样就会出现一个问题:a1a1a1 可能去到 b2b2b2

但此时我们可以交换 b1,b2b1,b2b1,b2 ,再跑一遍,如果仍然满流,那么答案就是 YesYesYes

这个做法看起来很神秘,为什么是对的 ???

接下来我们证明跑两次最大流,可以保证 a1a1a1 到 a2a2a2 的流合法 ((( b1b1b1 到 b2b2b2 是同理的 )))

我们不妨令第一次时符合标准的流为 xxx,则有 2×an−x2\times an-x2×an−x 的流不符合

此时交换,符合标准的流仍有 xxx ,所以不符合的也一定一定要有 2×an−x2\times an-x2×an−x

但此时的非法刘变成了 a1a1a1 到 b1b1b1 的,所以,我们可以依靠这些流,去 '修正' 前面 b1b1b1 到 a2a2a2 的非法流

所以是正确的

代码

cpp 复制代码
#include<bits/stdc++.h>
# define Maxn 65
# define inf 2e9+1
using namespace std;
int n,a1,a2,an,b1,b2,bn;
char a[Maxn][Maxn];

struct MaxFlow{
    int head[Maxn],tot=2;
    struct Node{int to,next,w;}e[Maxn*Maxn<<1];
    void add(int u,int v,int w) {e[tot]={v,head[u],w},head[u]=tot++;}
    void addedge(int u,int v,int w) {
        // printf("EDGE %d %d %d\n",u,v,w);
        add(u,v,w),add(v,u,0);
    }

    bool vis[Maxn];
    int dis[Maxn],cur[Maxn],st,ed;
    queue<int> q;
    bool bfs() {
        for(int i=1;i<=ed;i++) dis[i]=inf,cur[i]=head[i];
        while(!q.empty()) q.pop();
        dis[st]=0,q.push(st);
        while(!q.empty()) {
            int h=q.front();q.pop();
            for(int i=head[h];i;i=e[i].next) {
                if(e[i].w&&dis[e[i].to]==inf) {
                    dis[e[i].to]=dis[h]+1;
                    if(e[i].to==ed) return 1;
                    q.push(e[i].to);
                }
            }
        }
        return 0;
    }
    int dfs(int rt,int flow) {
        if(rt==ed) return flow;
        int res=0;
        for(int i=cur[rt];i;i=e[i].next) {cur[rt]=i;
            if(e[i].w&&dis[e[i].to]==dis[rt]+1) {
                int nowflow=dfs(e[i].to,min(e[i].w,flow));
                if(!nowflow) dis[e[i].to]=inf;
                else {
                    e[i].w-=nowflow;
                    e[i^1].w+=nowflow;
                    flow-=nowflow;
                    res+=nowflow;
                    if(!nowflow) return res;
                }
            }
        }
        return res;
    }

    void build() {
        st=n+1,ed=n+2;
        for(int i=1;i<=n;i++) {
            for(int j=1;j<=n;j++) {
                if(a[i][j]=='N') addedge(i,j,inf);
                if(a[i][j]=='O') addedge(i,j,2);
            }
        }
        addedge(st,a1,2*an),addedge(a2,ed,2*an);
        addedge(st,b1,2*bn),addedge(b2,ed,2*bn);
    }
    void clear() {
        for(int i=1;i<=ed;i++) head[i]=0;
        tot=2;
    }
    bool check() {
        clear(),build();
        int ans=0;
        while(bfs()) ans+=dfs(st,inf);
        return (ans==2*an+2*bn);
    }
}fl;

int main() {
    while(~scanf("%d%d%d%d%d%d%d",&n,&a1,&a2,&an,&b1,&b2,&bn)) {
        a1++,a2++,b1++,b2++;
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++) cin>>a[i][j];
        bool fg=0;
        if(fl.check()) {
            swap(b1,b2);
            if(fl.check()) fg=1;
        }
        fg?printf("Yes\n"):printf("No\n");
    }
    return 0;
}

P3849 [TJOI2007] 足彩投注

题意

我们现在考虑一个简化的模型。对于一轮比赛,彩民需要竞猜其中 nnn 场比赛的结果,每场比赛的胜负平都有一个概率 p(i,r)p(i, r)p(i,r) 。其中,iii 表示第 iii 场比赛。r∈{0,1,2}r\in \{0,1,2\}r∈{0,1,2},分别表示主队比赛结果的负、平、胜。p(i,r)p(i, r)p(i,r) 则表示第i场比赛、结果为r的概率。此外,还有一个概率 q(i,r)q(i, r)q(i,r),表示第 iii 场比赛,投注购买结果为 rrr 的概率,即总注数中购买该场次某一比赛结果的概率

例如,如果 q(1,0)=0.5q(1,0) = 0.5q(1,0)=0.5,我们可以知道第一场比赛有 50%50\%50% 的投注会买主队输球。我们假设这 nnn 场比赛互不相关,即 p(i,r)p(i, r)p(i,r) 的结果不会受 p(j,r')p(j, r')p(j,r') 的影响,q(i,r)q(i, r)q(i,r) 的结果也不会受 q(j,r')q(j, r')q(j,r') 的影响(r≠r'r \ne r'r=r')。

在这个模型里,我们规定,必须猜中全部 nnn 场比赛的结果才能获奖。总奖金为 MMM,由所有获奖的投注平分。因此,对于一个单式投注 Ri={ri1,ri2,...,rin}R_i = \{r_{i1}, r_{i2}, \ldots ,r_{in}\}Ri={ri1,ri2,...,rin},rijr_{ij}rij 表示投注 RiR_iRi对第j场比赛的预测结果,它的中奖概率为:

P(Ri)=∏j=1np(j,rij) P(R_i)=\prod\limits_{j=1}^np(j,r_{ij}) P(Ri)=j=1∏np(j,rij)

设投注总数为 NNN,那么中奖的投注总数为:

N⋅Q(Ri)=N⋅∏j=1nq(j,rij) N\cdot Q(R_i)=N\cdot\prod\limits_{j=1}^nq(j,r_{ij}) N⋅Q(Ri)=N⋅j=1∏nq(j,rij)

于是,投注 RiR_iRi 所能得到的奖金的期望(平均意义下能够获得的奖金数)就是:

MN⋅Q(Ri)⋅P(Ri) \dfrac{M}{N\cdot Q(R_i)} \cdot P(R_i) N⋅Q(Ri)M⋅P(Ri)

以上考虑的仅仅是单式投注的情况,即仅考虑单注 RiR_iRi 的中奖情况。对于复式投注,情况要复杂一些。采用复式投注时,投注的是一个集合 R={R1,R2,...,Rk}R = \{R1, R2, ..., Rk\}R={R1,R2,...,Rk},其中k是投注的数量。例如,三场比赛,第一场猜"胜负",第二场猜"平",第三场猜"负平",则 k=4k = 4k=4,RRR 集合所包含的四个元素如下如下:

ri1r_{i1}ri1 ri2r_{i2}ri2 ri3r_{i3}ri3
R1R_1R1 0 1 0
R2R_2R2 0 1 1
R3R_3R3 2 1 0
R4R_4R4 2 1 1

复式投注R中,只要有一个 RiRiRi 猜对所有比赛结果,即可中奖。因此,复式投注R所能获得的奖金的期望就是:

∑Ri∈RMN⋅Q(Ri)⋅P(Ri) \sum_{R_i\in R}\dfrac{M}{N\cdot Q(R_i)} \cdot P(R_i) Ri∈R∑N⋅Q(Ri)M⋅P(Ri)

我们的问题是,给定 nnn 场比赛的信息(胜负平的概率和彩民购买三种结果的概率),以及复式投注中可以购买的最大注数 UUU,要求设计一种复式投注的方案,在不超过最大注数(复式投注的注数 k≤Uk \le Uk≤U)的前提下,使得获得奖金的期望最大。

题解

我们令 K(i,j)=P(i,j)Q(i,j)K(i,j)=\frac{P(i,j)}{Q(i,j)}K(i,j)=Q(i,j)P(i,j)

那么式子就可以简化为

ln⁡MN+∑i+1nln⁡[[选择0]×K(i,0)+[选择1]×K(i,1)+[选择2]×K(i,2)]\ln{\frac{M}{N}}+\sum_{i+1}^n\ln \bigg[[选择 0] \times K(i,0)+[选择 1] \times K(i,1)+[选择 2] \times K(i,2)\bigg]lnNM+i+1∑nln[[选择0]×K(i,0)+[选择1]×K(i,1)+[选择2]×K(i,2)]

那么贪心肯定优先选 Ki,0,Ki,1,Ki,2K_{i,0},K_{i,1},K_{i,2}Ki,0,Ki,1,Ki,2 中大的

于是可以直接 DPDPDP

令 fi,jf_{i,j}fi,j 为考虑到第 iii 场比赛,注数为 jjj 的答案

时间复杂度 O(NU)O(NU)O(NU)

代码

cpp 复制代码
#include<bits/stdc++.h>
# define Maxn 10005
# define db double
using namespace std;
int n,N,M,U;
db p[Maxn][4],q[Maxn][4],K[Maxn][4],ans;
db f[2][Maxn];
bool cmp(db x,db y) {return x>y;}
int main() {
 $   scanf("%d%d%d%d",&n,&N,&M,&U);
    for(int i=1;i<=n;i++) {
        for(int j=0;j<3;j++) scanf("%lf",&p[i][j]);
        for(int j=0;j<3;j++) scanf("%lf",&q[i][j]),K[i][j]=p[i][j]/q[i][j];
        sort(K[i]+0,K[i]+3,cmp);
        for(int j=1;j<3;j++) K[i][j]+=K[i][j-1];
    }
    // printf("%.3lf\n",K[1][0]);
    for(int i=1;i<=n;i++) {
        for(int j=1;j<=U;j++) {
            if(j%1==0) f[i&1][j]=max(f[i&1][j],f[!(i&1)][j/1]+log(K[i][0]));
            if(j%2==0) f[i&1][j]=max(f[i&1][j],f[!(i&1)][j/2]+log(K[i][1]));
            if(j%3==0) f[i&1][j]=max(f[i&1][j],f[!(i&1)][j/3]+log(K[i][2]));
            if(i==n) ans=max(ans,f[i&1][j]);
        }
        for(int j=1;j<=U;j++) f[!(i&1)][j]=0;
    }
    printf("%.3lf\n",log((M*1.0)/N)+ans);
    return 0;
}

P4683 [IOI 2008] Type Printer

题意

给出 nnn 个长度至多 202020 的字符串,求出一个排列使相邻的字符串的最长公共前缀 +++ 最后一个字符串的长度最大

其中 n≤25000n\leq 25000n≤25000

赛时思路 以及 题解

容易想到,如果钦定了最后的单词

那么操作次数最小等价于最大化相邻单词的最长公共前缀

于是可以自然地想到字典树

显然按照字典序直接扫过去就是最小的

而对于最后一个单词,不难证明:无论钦定谁在最后,都存在一种方案使得我们能够最大化相邻单词的最长公共前缀

于是显然取最长的作为最后单词

时间复杂度 O(n×len)O(n\times len)O(n×len)

代码

cpp 复制代码
#include<bits/stdc++.h>
# define Maxn 25005
using namespace std;
int n,x,top,Cnt;
string s[Maxn];
char Ans[Maxn*50];

bool bz[Maxn*20],cnt[Maxn*20];
int trie[Maxn*20][29],tot;
void Insert(int x,int fg) {
    int rt=0;bz[rt]|=fg;
    for(int i=0;i<s[x].size();i++) {
        int ch=s[x][i]-'a';
        if(!trie[rt][ch]) trie[rt][ch]=++tot;
        rt=trie[rt][ch];
        bz[rt]|=fg;
    }
    cnt[rt]=1;
}
void dfs(int rt) {
    if(cnt[rt]) Ans[++top]='P',Cnt++;
    int P=-1;
    for(int i=0;i<26;i++) {
        if(trie[rt][i]) {
            if(!bz[rt]) Ans[++top]=i+'a',dfs(trie[rt][i]);
            else {
                if(!bz[trie[rt][i]]) Ans[++top]=i+'a',dfs(trie[rt][i]);
                else P=i;
            }
        }
    }
    if(P!=-1) Ans[++top]=P+'a',dfs(trie[rt][P]);
    if(Cnt!=n) Ans[++top]='-';
}

int main() {
    scanf("%d",&n);
    for(int i=1;i<=n;i++) {
        cin>>s[i];
        if(s[i].size()>s[x].size()) x=i;
    }
    for(int i=1;i<=n;i++) Insert(i,(x==i));
    dfs(0);

    printf("%d\n",top);
    for(int i=1;i<=top;i++) cout<<Ans[i]<<endl;
    return 0;
}
相关推荐
csdn_aspnet6 小时前
C语言 Lomuto分区算法(Lomuto Partition Algorithm)
c语言·开发语言·算法
谙弆悕博士7 小时前
【附C源码】从零实现C语言堆数据结构:原理、实现与应用
c语言·数据结构·算法··数据结构与算法
C+++Python9 小时前
C++ 进阶学习完整指南
java·c++·学习
sparEE9 小时前
c++值类别、右值引用和移动语义
开发语言·c++
jrrz082810 小时前
Apollo MPC Controller
c++·自动驾驶·apollo·mpc·横向控制·lateral control
gaosushexiangji10 小时前
DIC系统推荐:基于千眼狼三维数字图像相关的无人机旋翼疲劳试验全场应变与位移测量
人工智能·算法
小王C语言12 小时前
【线程概念与控制】:线程封装
jvm·c++·算法
学习,学习,在学习12 小时前
Qt工控仪器程序框架设计详解(工控多仪器控制版本)
开发语言·c++·qt
kyle~12 小时前
工程数学---点云配准卡布施(Kabsch)算法(求解最优旋转矩阵)
线性代数·算法·矩阵