123789456ye

已AFO

3.31考试总结

A

题面:51nod 1850
题解:概率DP
话说为什么背包对这些神仙们这么简单,直接带过去的啊
首先我们来看一下\(O(n^4)\)的做法
首先枚举人,枚举他抽到的牌\(O(n^2)\)
然后用背包转移\(O(n^2)\)
由于是用背包转移,所以要先对每个人的卡\(sort\)一遍
背包求出有\(k\)个人的卡比他大的概率
转移方程

\[f[l]=f[l-1]*prob[k]+f[l]*(1-prob[k]) \]

其中\(prob\)\(k\)这个人比他大的概率
初值\(f[0]=1,f[1...n]=0\)

\[ans[i]=\sum_{j=1}^{num[i]}\sum_{k=0}^{n-1} f[k]*v[k+1]*(1-\frac{g[i][j]}{100})*\frac{p[i][j]}{sum[i]} \]

表示\(i\)这个人选中\(j\)这张牌的概率为\(\frac{p[i][j]}{sum[i]}\),有\(k\)个人比他大的概率是\(f[k]\),获得的收益是\(v[k+1]*(1-\frac{g[i][j]}{100})\),然后求和

#include<bits/stdc++.h>
using namespace std;
inline void read(int& x)
{
	x = 0; char c = getchar();
	while (!isdigit(c)) c = getchar();
	while (isdigit(c)) x = x * 10 + c - '0', c = getchar();
}
#define maxn 205
const int P = 1e9 + 7;
int v[maxn], num[maxn], inv[maxn], sum[maxn];
int ans[maxn], f[maxn], pos[maxn], cost[maxn];
struct Person
{
	int a, g, p;
	bool operator < (const Person& q) const { return a > q.a; }
}p[maxn][maxn];
void exgcd(int a, int b, int& x, int& y)
{
	if (!b) { x = 1, y = 0; return; }
	exgcd(b, a % b, y, x);
	y -= a / b * x;
}
inline int getinv(int a)
{
	int x, y;
	exgcd(a, P, x, y);
	x %= P;
	return x > 0 ? x : x + P;
}
int main()
{
	int n; read(n);
	for (int i = 1; i <= n; ++i)
	{
		read(num[i]);
		for (int j = 1; j <= num[i]; ++j)
			read(p[i][j].a), read(p[i][j].g), read(p[i][j].p), sum[i] += p[i][j].p;
		sort(p[i] + 1, p[i] + num[i] + 1);//从大到小排序
		inv[i] = getinv(sum[i]);
	}
	for (int i = 1; i <= n; ++i) read(v[i]);
	int base = getinv(100);
	for (int i = 1; i <= n; ++i)
	{
		for (int j = 1; j <= n; ++j) pos[j] = 1, cost[j] = 0;
		for (int j = 1; j <= num[i]; ++j)
		{
			f[0] = 1;
			for (int k = 1; k <= n; ++k) f[k] = 0;
			for (int k = 1; k <= n; ++k)
			{
				if (i == k) continue;
				while (pos[k] <= num[k] && p[k][pos[k]].a > p[i][j].a)
					cost[k] += p[k][pos[k]].p, ++pos[k];//求出k这个人比他大的概率之和
				for (int tp, l = n; l >= 0; --l)
				{
					tp = 1ll * f[l] * (sum[k] - cost[k]) % P * inv[k] % P;
					if (l) tp = (0ll + tp + 1ll * f[l - 1] * cost[k] % P * inv[k] % P) % P;
					f[l] = tp;
				}
			}
			for (int k = 0; k < n; ++k)
				ans[i] = (1ll * ans[i] + 1ll * f[k] * (100 - p[i][j].g) % P * base % P * v[k + 1] % P * inv[i] % P * p[i][j].p % P) % P;
		}
	}
	for (int i = 1; i <= n; ++i) printf("%d\n", ans[i]);
	return 0;
}

然后看正解\(O(n^3)\)
对于一张卡,只有大于它的会对它的排名产生影响
将值\(A\)\(n\)个人中的期望排名写成生成函数的形式
如果大于\(A\)就会让其排名增大\(1\),所以令\(p_i\)为第\(i\)个人的选择大于\(A\)的概率

\[F(x)=\prod_{i=1}^{n}(p_ix+(1-p_i)) \]

对于一个人的选择\(A_i\),就直接把此时的多项式除掉第\(i\)个人的式子
算出期望排名之后,直接乘上概率和收益,累加进答案
由于这个多项式只有两项,所以可以暴力乘除
所以把所有卡按权值排序
每次移动,只会有一个多项式被改变
我们就维护各项系数,除掉这一项,算完贡献,再乘回去
然后我的做法是设\(g\)表示原多项式,\(f\)表示除掉了这一项的多项式
所以有$$g_x=f_{x-1}p_i+f_x(1-p_i)$$
初值\(g[0]=1,g[1...n-1]=0\)
这个很显然
然后逆推的话$$f_x=\frac{g_x-p_if_{x-1}}{1-p_i}$$

#include<bits/stdc++.h>
using namespace std;
template<typename T>
inline void read(T& x)
{
	x = 0; char c = getchar();
	while (!isdigit(c)) c = getchar();
	while (isdigit(c)) x = x * 10 + c - '0', c = getchar();
}
#define maxn 200005
#define P 1000000007
inline void exgcd(int a, int b, int& x, int& y)
{
	if (!b) { x = 1, y = 0; return; }
	exgcd(b, a % b, y, x);
	y -= a / b * x;
}
inline int getinv(int a)
{
	int x, y;
	exgcd(a, P, x, y);
	x %= P;
	return (x < 0) ? x + P : x;
}
int inv[maxn];
inline void init()//线性求逆元
{
	inv[1] = 1;
	for (int i = 2; i <= 200000; ++i) inv[i] = (0ll + P - 1ll * P / i * inv[P % i]) % P;
	//我一开始直接写了个阶乘逆元上去,然后调了半天……第一次写这种
}
struct Card
{
	int a, g, p, id;
	bool operator < (const Card& q) const { return a < q.a; }
}c[maxn];
int n, tot, v[maxn], sum[maxn], now[maxn], f[maxn], g[maxn], ans[maxn];
//now[x]就是目前的prob[x]
inline void insert(int x)//乘进去
{
	int prob = 1ll * now[x] * inv[sum[x]] % P, prob2 = (1 - prob + P) % P;
	g[0] = 1ll * f[0] * prob2 % P;
	for (int i = 1; i < n; ++i) g[i] = (1ll * f[i - 1] * prob + 1ll * f[i] * prob2) % P
}
inline void delet(int x)//除掉
{
	int prob = 1ll * now[x] * inv[sum[x]] % P, invprob = 1ll * inv[sum[x] - now[x]] * sum[x] % P;
	f[0] = 1ll * g[0] * invprob % P;
	for (int i = 1; i < n; ++i) f[i] = (0ll + g[i] - 1ll * f[i - 1] * prob % P + P) * invprob % P;
}
int main()
{
	init();
	read(n);
	for (int i = 1, m; i <= n; ++i)
	{
		read(m);
		while (m--)
		{
			read(c[++tot].a), read(c[tot].g), read(c[tot].p), c[tot].id = i;
			sum[i] += c[tot].p;
			c[tot].g = 1ll * (100 - c[tot].g) * inv[100] % P;
		}
	}
	sort(c + 1, c + tot + 1);
	for (int i = 1; i <= n; ++i) read(v[i]);
	g[0] = 1;
	for (int i = 1; i <= tot; ++i)
	{
		delet(c[i].id);
		for (int j = 0; j < n; ++j)
			ans[c[i].id] = (ans[c[i].id] + 1ll * f[j] * v[n - j] % P * c[i].g % P * c[i].p % P * inv[sum[c[i].id]] % P) % P;
		now[c[i].id] += c[i].p;
		insert(c[i].id);
	}
	for (int i = 1; i <= n; ++i) printf("%d\n", ans[i] < 0 ? ans[i] + P : ans[i]);
	return 0;
}

B 异或约数和

题面:51nod 1984
题解:数学
考虑一个数\(i\),它会被算进贡献里\(\left \lfloor \frac{n}{i} \right \rfloor\)
所以我们可以数论分块,如果余数为奇数就异或上这个区间
通过打表可以发现

\[xor_{i=1}^{x}= \begin{cases} &1&x\equiv 1~(~mod~4) \\ &x+1&x\equiv 2~(~mod~4) \\ &0&x\equiv 3~(~mod~4) \\ &x&x\equiv 0~(~mod~4) \end{cases} \]

复杂度:\(O(\sqrt n)\)

#include<bits/stdc++.h>
using namespace std;
template<typename T>
inline void read(T& x)
{
	x = 0; char c = getchar();
	while (!isdigit(c)) c = getchar();
	while (isdigit(c)) x = x * 10 + c - '0', c = getchar();
}
long long n, ans;
inline long long get(long long x)
{
	long long tp = x - ((x >> 2) << 2);
	if (tp == 1) return 1;
	if (tp == 2) return x + 1;
	if (tp == 3) return 0;
	return x;
}
int main()
{
	read(n);
	for (register long long l = 1, r, i; l <= n; l = r + 1)
	{
		r = n / (n / l);
		if ((n / l) & 1) ans ^= get(r) ^ get(l - 1);
	}
	printf("%lld\n", ans);
	return 0;
}

C 小朋友的笑话

题面:51nod 2014
题解:线段树+\(set\)维护区间
考虑到只有\(10^5\)种笑话,我们可以开\(10^5\)颗动态开点线段树
如果这个区间的人都听过或都没听过,就继续往下,否则在一颗普通线段树上区间赋值
然后我没调出来,样例第5个输出一直是5
可能需要开\(vector\)(因为直接开那么大的数组,还是\(10^5\)颗,必定MLE)


所以我们考虑用\(set\)来维护
建一个结构体\((L,R,Col)\)表示\([L,R]\)区间都是没听过/听过(0/1)
初始化每一种笑话都是\((1,n,0)\)
对于每一个修改操作\((l,r,y)\)表示\([l,r]\)区间的人听到了\(y\)笑话
我们二分一个\(L\)最大且小于等于\(l\)的区间
代码中的写法是因为重载了构造函数所以可以直接这样写,注意参数一定要默认0,给的参数按顺序
枚举\(L\le r\)的区间,删掉这个区间
注意一定不能把++it写到for上面—,否则会收获RE的好成绩
这是因为it当时已经被删掉了,所以一定要在删之前++it
反手一个区间赋值,闷声大发财
然后两个区间的交区间直接赋成\(!Col\)(听过了赋\(0\)
如果这个区间也是\(Col==1\)就两边合并一下
否则的话就两边剩余的区间插回去
最后把一整段为\(1\)的区间插回去
吐槽一下,为什么百度搜索前几个大仙的题解里面,线段树写的那么鬼啊
复杂度:\(O(n\log n)\)

#include<bits/stdc++.h>
using namespace std;
template<typename T>
inline void read(T& x)
{
	x = 0; char c = getchar();
	while (!isdigit(c)) c = getchar();
	while (isdigit(c)) x = x * 10 + c - '0', c = getchar();
}
#define maxn 100005
struct node
{
	int l, r, col;
	node(int l = 0, int r = 0, int col = 0) :l(l), r(r), col(col) {}
	bool operator < (const node& p) const { return l < p.l; }
};
set<node> s[maxn];
set<node>::iterator it, it2;
int n, m;
struct SegmentTree2
{
#define ls rt<<1
#define rs rt<<1|1
	int val[maxn << 2], flag[maxn << 2], len[maxn << 2];
	inline void pushup(int rt) { val[rt] = val[ls] + val[rs]; }
	inline void pd(int rt, int f)
	{
		if (f) val[rt] = len[rt];
		else val[rt] = 0;
		flag[rt] = f;
	}
	inline void pushdown(int rt)
	{
		if (flag[rt] == -1) return;
		pd(ls, flag[rt]), pd(rs, flag[rt]);
		flag[rt] = -1;
	}
	void build(int rt, int l, int r)
	{
		len[rt] = r - l + 1;
		flag[rt] = -1;
		if (l == r) return;
		int mid = (l + r) >> 1;
		build(ls, l, mid), build(rs, mid + 1, r);
	}
	void update(int rt, int l, int r, int fr, int to, int v)
	{
		if (fr <= l && to >= r) return pd(rt, v);
		int mid = (l + r) >> 1;
		pushdown(rt);
		if (fr <= mid) update(ls, l, mid, fr, to, v);
		if (to > mid) update(rs, mid + 1, r, fr, to, v);
		pushup(rt);
	}
	int query(int rt, int l, int r, int fr, int to)
	{
		if (fr <= l && to >= r) return val[rt];
		int mid = (l + r) >> 1, ans = 0;
		pushdown(rt);
		if (fr <= mid) ans += query(ls, l, mid, fr, to);
		if (to > mid) ans += query(rs, mid + 1, r, fr, to);
		return ans;
	}
#undef ls
#undef rs
}S;
int main()
{
	read(n), read(m);
	S.build(1, 1, n);
	for (int i = 1; i <= 100000; ++i) s[i].insert(node(1, n, 0));
	for (int i = 1, op, x, y, z; i <= m; ++i)
	{
		read(op);
		if (op == 1)
		{
			read(x), read(y), read(z);
			x -= z, z = z * 2 + x;
			x = max(1, x), z = min(n, z);
			it = s[y].upper_bound(x);
			if (it != s[y].begin()) --it;
			int l = x, r = z;
			for (; it != s[y].end() && (*it).l <= z;)
			{
				int L = (*it).l, R = (*it).r, Col = (*it).col;
				S.update(1, 1, n, max(L, x), min(R, z), !Col);
				if (Col) l = min(l, L), r = max(r, R);
				it2 = it; ++it; s[y].erase(it2);
				if (!Col && L < x) s[y].insert(node(L, x - 1, 0));
				if (!Col && R > z) s[y].insert(node(z + 1, R, 0));
			}
			s[y].insert(node(l, r, 1));
		}
		else
			read(x), read(y), printf("%d\n", S.query(1, 1, n, x, y));
	}
	return 0;
}
posted @ 2020-04-04 11:41  123789456ye  阅读(100)  评论(0编辑  收藏  举报