『7.3 NOIP模拟赛题解』

<更新提示>


<正文>

T1 gift

Description

​ 夏川的生日就要到了。作为夏川形式上的男朋友,季堂打算给夏川买一些生日礼物。

​ 商店里一共有种礼物。夏川每得到一种礼物,就会获得相应喜悦值Wi(每种礼物的喜悦值不能重复获得)。

​ 每次,店员会按照一定的概率Pi(或者不拿出礼物),将第i种礼物拿出来。季堂每次都会将店员拿出来的礼物买下来。没有拿出来视为什么都没有买到,也算一次购买。

​ 众所周知, 白毛切开都是黑的。 所以季堂希望最后夏川的喜悦值尽可能地高。

​ 求夏川最后最大的喜悦值是多少,并求出使夏川得到这个喜悦值,季堂的期望购买次数。

Input Format

​ 第一行,一个整数N,表示有N种礼物。

​ 接下来N行,每行一个实数Pi和正整数Wi,表示第i种礼物被拿出来的概率和可以获得喜悦值。

Output Format

​ 第一行,一个整数表示可以获得的最大喜悦值。

​ 第二行,一个实数表示获得这个喜悦值的期望购买次数,保留3位小数。

Sample Input

3
0.1 2
0.2 5
0.3 7

Sample Output

14
12.167

Hint

对于10%的数据,N = 1

对于30%的数据,N ≤ 5

对于100%的数据,N ≤ 20 ,0 < Wi ≤ 10^9 ,0 < Pi ≤ 1且∑Pi ≤ 1

解析

第一问显然是所有喜悦值的累加和,问题在于第二问买到每一种礼物的期望天数。

观察数据范围后,很容易想到是状态压缩\(dp\),但是发现样例完全不可推,于是转移的思路有点难找。

当然,状态一定是\(f[S]\)代表买到了礼物状态为\(S\)时的期望天数,那么目标状态为\(f[2^n-1]\)。考虑如何转移,显然,由于给出的概率是针对每一天拿出某个物品的概率,那么我们转移也从每一天的角度考虑:

\[f_S=\sum_{S'\cap i= S}(f_{S'}*p_i)+(1-\sum_{i\in S}p_i)f_S+1
\]

方程的含义为:对于状态\(S\),我们考虑取出物品的每一种可能,要么取到了集合中的某一个物品,要么取到了集合外的物品或没取到物品,概率乘上各自的期望再加上取这一次的\(1\)就是当前状态的期望。

将状态转移方程移项可以得到:

\[\sum_{i\in S}p_if_S=\sum_{S'\cap i=S}f_{S'}p_i+1
\]

直接计算即可。

\(Code:\)

#include<bits/stdc++.h>
using namespace std;
const int N = 21;
int n,w[N],cnt[1<<N];
double p[N],f[1<<N];
long long sum;
inline void input(void)
{
scanf("%d",&n);
for (int i=1;i<=n;i++)
scanf("%lf%d",&p[i],&w[i]),
sum += w[i];
}
inline void dp(void)
{
f[0] = 0;
for (int S=1;S<1<<n;S++)
cnt[S] = cnt[ S & (S-1) ] + 1;
for (int S=1;S<1<<n;S++)
{
double sump = 0.0;
for (int i=1;i<=n;i++)
if ( S >> (i-1) & 1 )
f[S] += p[i] * f[S^(1<<(i-1))] , sump += p[i];
f[S] = ( f[S] + 1 ) / sump;
}
}
int main(void)
{
input();
dp();
printf("%lld\n%.3lf\n",sum,f[(1<<n)-1]);
return 0;
}

通讯

Description

​ “这一切都是命运石之门的选择。”

​ 试图研制时间机器的机关SERN截获了中二科学家伦太郎发往过去的一条短信,并由此得知了伦太郎制作出了电话微波炉(仮)。

​ 为了掌握时间机器的技术,SERN总部必须尽快将这个消息通过地下秘密通讯网络,传达到所有分部。

​ SERN共有N个部门(总部编号为0),通讯网络有M条单向通讯线路,每条线路有一个固定的通讯花费Ci。

​ 为了保密,消息的传递只能按照固定的方式进行:从一个已知消息的部门向另一个与它有线路的部门传递(可能存在多条通信线路)。我们定义总费用为所有部门传递消息的费用和。

​ 幸运的是,如果两个部门可以直接或间接地相互传递消息(即能按照上述方法将信息由X传递到Y,同时能由Y传递到X),我们就可以忽略它们之间的花费。

​ 由于资金问题(预算都花在粒子对撞机上了),SERN总部的工程师希望知道,达到目标的最小花费是多少。

Input Format

​ 多组数据,文件以2个0结尾。

​ 每组数据第一行,一个整数N,表示有N个包括总部的部门(从0开始编号)。然后是一个整数M,表示有M条单向通讯线路。

​ 接下来M行,每行三个整数,Xi,Yi,Ci,表示第i条线路从Xi连向Yi,花费为Ci。

Output Format

​ 每组数据一行,一个整数表示达到目标的最小花费。

Sample Input

3 3
0 1 100
1 2 50
0 2 100
3 3
0 1 100
1 2 50
2 1 100
2 2
0 1 50
0 1 100
0 0

Sample Output

150
100
50

Hint

​ 对于10%的数据,保证M=N-1

​ 对于另30%的数据,N ≤ 20 ,M ≤ 20

​ 对于100%的数据,N ≤ 50000 ,M ≤ 10^5 ,Ci ≤ 10^5 ,数据组数 ≤ 5

​ 数据保证一定可以将信息传递到所有部门。

解析

显然,忽略花费的点一定在有向图的强连通分量里,那么只需用\(tarjan\)对原图进行\(SCC\)缩点,原问题就转化成了最小树形图问题。

这个最小树形图问题是在缩点后的\(DAG\)上的,所以可以直接对\(DAG\)执行拓扑排序,在过程中贪心选取最小边权的边即可。

\(Code:\)

#include<bits/stdc++.h>
using namespace std;
const int N = 50020 , M = 1e5+20;
struct edge{int ver,val,next;}e[M],e_[M];
int n,m,t,Head[M],dfn[N],low[N],in[N],Head_[M],t_;
int con[N],Stack[N],ins[N],top,num,cnt;
long long ans;
priority_queue < int , vector<int> , greater<int> > Heap[N];
inline void insert(int x,int y,int v)
{
e[++t] = (edge){y,v,Head[x]} , Head[x] = t;
}
inline void insert_(int x,int y,int v)
{
e_[++t_] = (edge){y,v,Head_[x]} , Head_[x] = t_;
}
inline int read(void)
{
int x = 0 , w = 0; char ch = ' ';
while ( !isdigit(ch) ) w |= ch=='-' , ch = getchar();
while ( isdigit(ch) ) x = x*10 + ch-48 , ch = getchar();
return w ? -x : x;
}
inline void input(void)
{
for (int i=1;i<=m;i++)
{
int x = read()+1 , y = read()+1 , v = read();
insert( x , y , v );
}
}
inline void Tarjan(int x)
{
dfn[x] = low[x] = ++num;
Stack[++top] = x , ins[x] = 1;
for (int i=Head[x];i;i=e[i].next)
{
int y = e[i].ver;
if ( !dfn[y] )
{
Tarjan(y);
low[x] = min( low[x] , low[y] );
}
else if ( ins[y] )
low[x] = min( low[x] , dfn[y] );
}
if ( dfn[x] == low[x] )
{
++cnt; int T = 0;
do
{
T = Stack[top--] , ins[T] = 0;
con[T] = cnt;
}
while ( x != T );
}
}
inline void rebuild(void)
{
for (int i=1;i<=n;i++)
{
for (int j=Head[i];j;j=e[j].next)
{
int x = i , y = e[j].ver;
if ( con[x] == con[y] ) continue;
insert_( con[x] , con[y] , e[j].val );
in[ con[y] ]++;
}
}
}
inline void Topsort(void)
{
queue < int > q;
for (int i=1;i<=cnt;i++)
if ( !in[i] ) q.push(i);
while ( !q.empty() )
{
int temp = q.front();
q.pop();
if ( Heap[temp].size() )
ans += Heap[temp].top();
for (int i=Head_[temp];i;i=e_[i].next)
{
int next = e_[i].ver;
in[next]-- , Heap[next].push(e_[i].val);
if ( !in[next] ) q.push(next);
}
}
}
inline void reset(void)
{
for (int i=1;i<=cnt;i++)
while ( Heap[i].size() )
Heap[i].pop();
num = cnt = top = t = t_ = 0;
ans = 0LL;
memset( Head , 0 , sizeof Head );
memset( Head_ , 0 , sizeof Head_ );
memset( dfn , 0 , sizeof dfn );
memset( low , 0 , sizeof low );
memset( in , 0 , sizeof in );
memset( ins , 0 , sizeof ins );
memset( con , 0 , sizeof con );
}
int main(void)
{
while ( scanf("%d%d",&n,&m) && n && m )
{
input();
for (int i=1;i<=n;i++)
if ( !dfn[i] ) Tarjan(i);
rebuild();
Topsort();
printf("%lld\n",ans);
reset();
}
return 0;
}

奇袭

Description

​ 由于各种原因,桐人现在被困在Under World(以下简称UW)中,而UW马上要迎来最终的压力测试——魔界入侵。

​ 唯一一个神一般存在的Administrator被消灭了,靠原本的整合骑士的力量是远远不够的。所以爱丽丝动员了UW全体人民,与整合骑士一起抗击魔族。

​ 在UW的驻地可以隐约看见魔族军队的大本营。整合骑士们打算在魔族入侵前发动一次奇袭,袭击魔族大本营!

​ 为了降低风险,爱丽丝找到了你,一名优秀斥候,希望你能在奇袭前对魔族大本营进行侦查,并计算出袭击的难度。

​ 经过侦查,你绘制出了魔族大本营的地图,然后发现,魔族大本营是一个N×N的网格图,一共有N支军队驻扎在一些网格中(不会有两只军队驻扎在一起)。

​ 在大本营中,每有一个k×k(1≤k≤N)的子网格图包含恰好k支军队,我们袭击的难度就会增加1点。

​ 现在请你根据绘制出的地图,告诉爱丽丝这次的袭击行动难度有多大。

Input Format

​ 第一行,一个正整数N,表示网格图的大小以及军队数量。

​ 接下来N行,每行两个整数,Xi,Yi,表示第i支军队的坐标。

​ 保证每一行和每一列都恰有一只军队,即每一个Xi和每一个Yi都是不一样的。

Output Format

​ 一行,一个整数表示袭击的难度。

Sample Input

5
1 1
3 2
2 4
5 5
4 3

Sample Output

10

Hint

​ 对于30%的数据,N ≤ 100

​ 对于60%的数据,N ≤ 5000

​ 对于100%的数据,N ≤ 50000

解析

由于军队的横纵坐标各不相同,所以原问题可以转换为序列问题:给定一个\(1-n\)的排列,求这个序列中有多少个子序列满足子序列中的数字恰好是连续的一串数。很容易可以知道,子区间\([l,r]\)符合要求,就会有\(max_{l,r}-min_{l,r}=r-l\),所以我们从最值入手,考虑一个分治算法。

定义分治函数\(divide(l,r)\)统计区间\([l,r]\)的答案,那么落在区间\([l,mid]\)和\([mid+1,r]\)的答案可以递归处理。问题在于统计跨过中点\(mid\)的合法子序列。我们根据最值来讨论,若最大最小值都在\(mid\)左边,那么我们可以直接枚举区间左端点,然后利用上述性质计算区间长度,得到区间右端点,在判断是否合法即可。

若最小值在\(mid\)左边,最大值在\(mid\)右边,我们可以对上述式子做一下转换:\(max_{l,r}-r=min_{l,r}-l\),那么只需将等式一边的值先塞在桶里,再枚举计算另一边的值就能累加答案。但是,可能会出现满足等式但不满足小值在\(mid\)左边,最大值在\(mid\)右边的前提条件的情况,所以我们要在满足条件的前提下加点。根据最小值的单调不增性,我们可以找到区间最右点并满足最小值在左边,根据最大值的单调不减性,我们可以找到区间最左点并满足最大值在右边,那这个区间内的任意一个点都可能成为答案的右端点,根据桶来统计即可。

当然,还有最大最小值都在\(mid\)右边,最大值在\(mid\)左边,最小值在\(mid\)右边两种情况,方法是一样的。

\(Code:\)

#include<bits/stdc++.h>
using namespace std;
const int N = 300020 , INF = 1<<30;
int n,a[N],Min[N],Max[N];
long long ans,record[2*N];
inline void input(void)
{
scanf("%d",&n);
for (int i=1;i<=n;i++)
{
int x,y; scanf("%d%d",&x,&y);
a[x] = y;
}
}
inline void divide(int l,int r)
{
if ( l == r ) return;
int mid = l+r >> 1;
divide( l , mid ) , divide( mid+1 , r );
Min[mid] = Max[mid] = a[mid];
Min[mid+1] = Max[mid+1] = a[mid+1];
for (int i=mid+2;i<=r;i++)
Min[i] = min( Min[i-1] , a[i] ) , Max[i] = max( Max[i-1] , a[i] );
for (int i=mid-1;i>=l;i--)
Min[i] = min( Min[i+1] , a[i] ) , Max[i] = max( Max[i+1] , a[i] );
for (int i=mid;i>=l;i--)
{
int R = Max[i] - Min[i] + i;
if ( R > mid && R <= r && Max[R] < Max[i] && Min[R] > Min[i] ) ans++;
}
for (int i=mid+1;i<=r;i++)
{
int L = i - Max[i] + Min[i];
if ( L <= mid && L >= l && Max[L] < Max[i] && Min[L] > Min[i] ) ans++;
}
int head = mid+1 , tail = mid+1;
for (int i=mid;i>=l;i--)
{
while ( tail <= r && Min[tail] > Min[i] )
++record[ Max[tail] - tail + n ] , ++ tail;
while ( head < tail && Max[head] < Max[i] )
--record[ Max[head] - head + n ] , ++ head;
ans += record[ Min[i] - i + n ];
}
while ( head < tail )
--record[ Max[head] - head + n ] , ++ head;
head = mid , tail = mid;
for (int i=mid+1;i<=r;i++)
{
while ( head >= l && Min[head] > Min[i] )
++record[ Max[head] + head ] , -- head;
while ( tail > head && Max[tail] < Max[i] )
--record[ Max[tail] + tail ] , -- tail;
ans += record[ Min[i] + i ];
}
while ( head < tail )
--record[ Max[tail] + tail ] , -- tail;
}
int main(void)
{
input();
divide( 1 , n );
printf("%lld\n",ans+(long long)n);
return 0;
}

<后记>

上一篇:(5)TCP和UDP协议


下一篇:【转】GitHub删除一个仓库——2013-08-27 21