『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;
}

转载于:https://www.cnblogs.com/Parsnip/p/11135017.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
面对Hash函数领域取得的重大研究进展,Crypto 2004 会议总主席StorageTek高级研究员Jim Hughes 17 日早晨表示,此消息太重要了,因此他已筹办该会成立24年来的首次网络广播(Webcast )。Hughes在会议上宣布:“会中将提出三份探讨杂凑碰撞(hash collisions )重要的研究报告。”其中一份是王小云等几位中国研究人员的研究发现。17日晚,王小云教授在会上把他们的研究成果做了宣读。这篇由王小云、冯登国、来学嘉、于红波四人共同完成的文章,囊括了对MD5、HAVAL-128、 MD4和RIPEMD四个著名HASH算法的破译结果。在王小云教授仅公布到他们的第三个惊人成果的时候,会场上已经是掌声四起,报告不得不一度中断。报告结束后,所有与会专家对他们的突出工作报以长时的热烈掌声,有些学者甚至起立鼓掌以示他们的祝贺和敬佩。当人们掌声渐息,来学嘉教授又对文章进行了一点颇有趣味的补充说明。由于版本问题,作者在提交会议论文时使用的一组常数和先行标准不同;在会议发现这一问题之后,王小云教授立即改变了那个常数,在很短的时间内就完成了新的数据分析,这段有惊无险的小插曲倒更加证明了他们论文的信服力,攻击方法的有效性,反而凸显了研究工作的成功。   会议结束时,很多专家围拢到王小云教授身边,既有简短的探讨,又有由衷的祝贺,褒誉之词不绝。包含公钥密码的主要创始人R. L. Rivest和A. Shamir在内的世界顶级的密码学专家也上前表示他们的欣喜和祝贺。   国际密码学专家对王小云教授等人的论文给予高度评价。   MD5的设计者,同时也是国际著名的公钥加密算法标准RSA的第一设计者R.Rivest在邮件中写道:“这些结果无疑给人非常深刻的印象,她应当得到我最热烈的祝贺,当然,我并不希望看到MD5就这样倒下,但人必须尊崇真理。”   Francois Grieu这样说:“王小云、冯登国、来学嘉和于红波的最新成果表明他们已经成功破译了MD4、MD5、HAVAL-128、RIPEMD-128。并且有望以更低的复杂度完成对SHA-0的攻击。一些初步的问题已经解决。他们赢得了非常热烈的掌声。”   另一位专家Greg Rose如此评价:“我刚刚听了Joux和王小云的报告,王所使用的技术能在任何初始值下用2^40次hash运算找出SHA-0的碰撞。她在报告中对四种HASH函数都给出了碰撞,她赢得了长时间的起立喝彩,(这在我印象中还是第一次)。…… 她是当今密码学界的巾帼英雄。……(王小云教授的工作)技术虽然没有公开,但结果是无庸质疑的,这种技术确实存在。…… 我坐在Ron Rivest前面,我听到他评论道:‘我们不得不做很多的重新思考了。’”

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值