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)
那么式子就可以简化为
lnMN+∑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;
}