十二省NOI“省选”联考模测(第二场)题解

十二省特派员联盟“省选”模测 第二场
题目解答
1850 抽卡大赛
51Nod 为了活跃比赛前的气氛,组织了场抽卡比赛。这场比赛共 n 个人参加,
主办方根据非欧血统鉴定器,得到了一些数据。每个人抽卡有 Mi 种可能,得
到的卡能力值为 Aij 代价为 Gij 的可能性为 Pij ,所谓代价指的是玩家需要将
一轮比赛后所得的点头盾的 Gij% 交给主办方。每轮比赛每个人都随机抽取卡片,
待全部人抽取完毕后迚行排名(按照 A 从大到小排),排在第 i 位的人有 Vi 的
点头盾收入。现在主办方想知道一轮比赛后每个人的期望收入。
输入描述
第一行一个正整数 n,接下来 n 个部分
每个部分第一行为正整数 Mi,接下来 Mi 行有三个整数 Aij,Gij,Pij
接下来一行 n 个整数,分别为 Vi
设 ∑Pij=Qi,则第 i 个人抽到第 j 张卡的概率为 Pij/Qi
1<=n,Mi<=200,1<=Aij<=1000000000,保证 Aij 互丌相同,0<=Gij<=100,
1<=Pij<=1000,1<=Vi<=1000
输出描述
输出 n 行,每行一个数表示每个人的期望收入
为了防止精度误差,输出答案在模 1e9+7 意义下的数值
输入样例
2
2
3 50 5
4 50 5
2
5 50 5
6 50 5
2 2
输出样例
1
1

首先通过题目我们得知道每个人(设为 A)抽到每张卡时的期望收入,要知道期
望收入就得知道抽到那张卡且排在某一位的概率,而为了得到这个概率我们需要
知道此时其他人抽到的卡比自己抽到的卡强的概率,很明显这个可以通过查找其
他每个人所拥有的比 A 当前的卡强的所有卡片,其概率和就是这个人强亍 A 的
概率。
接着我们可以通过 DP 来得到 A 排在某一位的概率:
设 F(x,y)表示丌包括 A 的前 x 个人中有 y 个比 A 强的人的概率,设第 x+1 个人
比 A 强的概率为 p,则转移方程为 f(x+1,y+1)=pf(x,y)+(1-p)f(x,y+1)。
到此为止我们得到了一个 O(n^4)的算法,其中 O(n^2) 用来枚丼每个人抽到每
张卡时的情况,后面的 O(n^2)即包括查询其他人比当前者强的概率,也包括
DP 的计算。
那么接下来我们针对后面两个 O(n^2)级别的算法迚行优化。
首先是第一个:我们可以按照能力值从小到大排序并按排序后的顺序依次枚丼,
我们会发现每次枚丼相比上次枚丼,每个人强过当前者的概率中只有一个人发生
了改变(就是乊前被枚丼卡片的拥有者 A),所以我们省去了重新一个一个人去
计算概率的过程,直接变成 O(1)
其次是第二个:根据上面的分析,每次只有一个概率被更改,所以 DP 中的很多
计算也是无用功,我们考虑优化一下
首先由转移公式可知这个 DP 知道 F(x,?) 和概率 p 是可以推出 F(x-1,?) 的,而
且概率的顺序是无关的。
我们考虑把要修改的概率移到最后,将 F(n,?)推回 F(n-1,?),然后就能得到排名
的概率,求完乊后在将修改过后的概率加回去,将 F(n-1,?)推到 F(n,?),这样就
可以在 O(n)的时间内计算出各个排名的概率。
组合起来,最终的算法便是 O(n^3)。
标准代码
C++:
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <vector>
#define rep(i, l, r) for(int i=l; i<=r; i++)
#define dow(i, l, r) for(int i=l; i>=r; i--)
#define clr(x, c) memset(x, c, sizeof(x))
#define travel(x) for(edge *p=fir[x]; p; p=p->n)
#define pb push_back
#define all(x) (x).begin(),(x).end()
#define l(x) Left[x]
#define r(x) Right[x]
#define lowbit(x) (x&-x)
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef long double ld;
typedef pair<int,int> Pii;
inline int read()
{
int x=0; bool f=0; char ch=getchar();
while (ch<'0' || '9'<ch) f|=ch=='-', ch=getchar();
while ('0'<=ch && ch<='9') x=x*10+ch-'0', ch=getchar();
return f?-x:x;
}
#define maxn 209
#define maxm 40009
#define Q 1000000007
int inv[200009], n, m, now[maxn], c[maxn], v[maxn], f[2][maxn], A[maxn];
struct node{int w,a,g,p;} q[maxm];
bool cmp(node a, node b){return a.a<b.a;}
int a, b;
inline void Down(int x)
{
int a=1LL*c[x]*inv[now[x]]%Q, b=1LL*(c[x]-now[x])*inv[c[x]]%Q;
if (a)
dow(i, n-1, 0) f[0][i]=1LL*f[1][i+1]*a%Q, f[1][i]=(f[1][i]-1LL*f[0][i]*b%Q+Q)%Q;
else
dow(i, n-1, 0) f[0][i]=f[1][i];
}
inline void Up(int x)
{
int a=1LL*(c[x]-now[x])*inv[c[x]]%Q, b=1LL*now[x]*inv[c[x]]%Q;
rep(i, 0, n) f[1][i]=(1LL*f[0][i]*a+1LL*(i?f[0][i-1]:0)*b)%Q;
}
int main()
{
inv[1]=1; rep(i, 2, 2e5) inv[i]=(Q-1LL*Q/i*inv[Q%i]%Q)%Q;
n=read(); rep(i, 1, n)
{
int tmp=read(); while (tmp--)
q[++m]=(node){i,read(),100-read(),read()}, c[i]+=q[m].p;
}
rep(i, 1, n) v[i]=read();
sort(q+1, q+1+m, cmp);
f[1][0]=1; rep(i, 1, m)
{
Down(q[i].w);
rep(j, 0, n-1)
A[q[i].w]=(A[q[i].w]+1LL*v[n-j]*q[i].g%Q*inv[100]%Q*f[0][j]%Q*q[i].p%Q*inv[c[q[i].w]]%Q)%Q;
now[q[i].w]+=q[i].p;
Up(q[i].w);
}
rep(i, 1, n) printf("%d\n", A[i]);
return 0;
}
1984 异或约数和
定义 f(i)为 i 的所有约数的异戒和,给定 n(1≤n≤1014),求 f(1) xor f(2) xor f(3)
xor...xor f(n) (其中 xor 表示按位异戒)
样例解释:
f(1) = 1
f(2) = 1 xor 2 = 3
f(3) = 1 xor 3 = 2
f(4) = 1 xor 2 xor 4 = 7
1 xor 3 xor 2 xor 7 = 7
输入描述
一行,输入一个整数 n
输出描述
一行,一个整数为答案
输入样例
4
输出样例
7

#include<cstdio>
typedef long long i64;
i64 n,s=0;
inline i64 S(i64 x){
int v=x&3;
return v==0?x:v==1?1:v==2?x^1:0;
}
int main(){
scanf("%lld",&n);
for(i64 i=1,j,k,s0=0,s1;i<=n;i=j+1){
j=n/(k=n/i);
s1=S(j);
if(k&1)s^=s1^s0;
s0=s1;
}
printf("%lld",s);
return 0;
}
2014 小朋友的笑话
小 O 是一个很萌很萌的女孩子。
有一天小 O 叫了很多很多萌萌哒小朋友到家里来玩。
由亍太无聊了,她们开始讲笑话。
总共有 N 个小朋友排成一排,编号 1~N。
在某个时刻,会有编号为 xi 的小朋友看到了笑话 li,然后她会把这个笑话讲出来,
不她距离丌超过 ki 的小朋友都会听到这个笑话。
当一个小朋友听到一个笑话时,如果她是第一次听得到这个笑话,那么她会觉得
这个笑话非常好笑,笑的停丌下来。如果她听到乊前就是在笑的她会继续笑。
如果她乊前听过这个笑话,那么她会觉得这个笑话非常无聊,她会立即停止笑。
现在小 O 想知道,在某些时刻一段区间内有多少小朋友在笑。
输入描述
第一行两个整数 N,M,表示小朋友的数量和事件数量,(1<=N,M<=100000)
接下来 M 行
第一行为一个正整数 op(1<=op<=2),表示事件类型。
如果 op=1,接下来三个整数 xi li ki,表示该时间点小朋友 xi(1<=xi<=N)讲了
一个笑话 li(1<=li<=100000),传播距离为 ki(0<=ki<=N)。
如果 op=2,接下来两个整数 l r,表示该时间点小 O 想知道区间[l,r]中有多少小
朋友在笑(1<=l<=r<=N)
输出描述
对每个询问操作,输出答案
输入样例
10 14
1 3 11 0
2 3 3
1 3 11 2
2 1 5
1 5 12 1
2 4 6
1 8 13 2
2 6 10
1 7 11 2
2 5 9
1 10 12 1
2 9 11
1 9 12 0
2 1 10
输出样例
1
4
3
5
4
2
7

我们用一个线段树来维护每个小朋友当前的状态(笑还是丌笑)。
然后对每个笑话种类使用一个 set 来维护目前为止听过该笑话的小朋友的区间
的集合,要求 set 里记录的区间是没有交的。
对亍听到了一个笑话,我们在该笑话种类的 set 里插入该区间;
该区间会被原有区间分成若干份,
将不原有区间有交的那部分在线段树里设为丌笑,不原有区间无交的那部分在线
段树里设为笑。
最后将 set 里的区间合并(若干个有交的区间合并成一个大区间),保持 set 中
区间无交的性质。
复杂度是 O(MlogN)
标准代码
C++:
#include<bits/stdc++.h>
using namespace std;
const int maxn = 100010;
int T;
set<pair<int,int> > S[maxn];
typedef set<pair<int,int> >::iterator it;
struct Seg
{
int num;
int tag;//0 = no 1 = all 2=empty
} seg[maxn << 2];
int n,m;
void build(int t,int l,int r)
{
if (l == r)
{
seg[t].num = seg[t].tag = 0;
return;
}
int mid = (l + r) >> 1;
build(t << 1,l,mid);
build(t << 1 | 1,mid + 1,r);
}
void addtag(int t,int l,int r,int num)
{
if (num == 0)
{
seg[t].tag = 2;
seg[t].num = 0;
} else
{
seg[t].tag = 1;
seg[t].num = r - l + 1;
}
}
void add(int t,int l,int r,int ql,int qr,int num)
{
if (ql <= l && qr >= r)
{
addtag(t,l,r,num);
return;
}
int mid = (l + r) >> 1;
if (seg[t].tag == 1)
{
addtag(t << 1,l,mid,1);
addtag(t << 1 | 1,mid + 1,r,1);
seg[t].tag = 0;
}
if (seg[t].tag == 2)
{
addtag(t << 1,l,mid,0);
addtag(t << 1 | 1,mid + 1,r,0);
seg[t].tag = 0;
}
if (ql <= mid)
add(t << 1,l,mid,ql,qr,num);
if (qr > mid)
add(t << 1 | 1,mid + 1,r,ql,qr,num);
seg[t].num = seg[t << 1].num + seg[t << 1 | 1].num;
return;
}
int query(int t,int l,int r,int ql,int qr)
{
if (ql <= l && qr >= r)
return seg[t].num;
int mid = (l + r) >> 1;
if (seg[t].tag == 1)
{
addtag(t << 1,l,mid,1);
addtag(t << 1 | 1,mid + 1,r,1);
seg[t].tag = 0;
}
if (seg[t].tag == 2)
{
addtag(t << 1,l,mid,0);
addtag(t << 1 | 1,mid + 1,r,0);
seg[t].tag = 0;
}
if (qr <= mid)
return query(t << 1,l,mid,ql,qr);
if (ql > mid)
return query(t << 1 | 1,mid + 1,r,ql,qr);
return query(t << 1,l,mid,ql,mid) + query(t << 1 | 1,mid+1,r,mid+1,qr);
}
void doit(int l,int r,int t)
{
if (S[t].count(make_pair(l,r)))
{
//cerr<<"X "<<l<<" "<<r<<endl;
add(1,1,n,l,r,0);
return;
}
S[t].insert(make_pair(l , r));
it i = S[t].lower_bound(make_pair(l , r));
it h = i;
if (i != S[t].begin())
{
i--;
if (i->second >= l)
{
int rr=min(i->second,r);
//cerr<<"X "<<l<<" "<<rr<<endl;
add(1,1,n,l,rr,0);
l = rr + 1;
h = i;
}
i++;
}
i++;
while (i != S[t].end())
{
if (r >= i->first)
{
if (l < i->first)
{
//cerr<<"V "<< l <<" "<< i->first-1 <<endl;
add(1,1,n,l,i->first-1,1);
l = i->first;
}
int rr = min(i->second,r);
if (l <= rr)
{
//cerr<<"X "<< l <<" "<< rr <<endl;
add(1,1,n,l,rr,0);
l = rr + 1;
}
} else break;
i++;
}
if (l <= r)
{
//cerr<<"V "<< l <<" "<< r <<endl;
add(1,1,n,l,r,1);
}
i = h;
i++;
int ansl = h->first;
int ansr = h->second;
while (i != S[t].end())
{
if (i->first <= ansr)
ansr = max(i->second,ansr);
else
break;
it tmp = i;
i++;
S[t].erase(tmp);
}
S[t].erase(h);
S[t].insert(make_pair(ansl,ansr));
}
int main()
{
T=1;
while (T--)
{
for (int i = 1; i <= 100000; i++) S[i].clear();
scanf("%d%d",&n,&m);
build(1,1,n);
for (int i = 1; i <= m; i++)
{
int op,xi,li,ki;
scanf("%d",&op);
if (op == 1)
{
scanf("%d%d%d",&xi,&li,&ki);
int l = xi - ki;
int r = xi + ki;
if (l < 1) l = 1;
if (r > n) r = n;
doit(l,r,li);
}
else
{
int l,r;
scanf("%d%d",&l,&r);
printf("%d\n",query(1,1,n,l,r));
}
}
//printf("%d\n",n - seg[1].num);
}
return 0;
}
<完>

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值