test20191014

题面

题面

$T1$

Solution

首先,我们不考虑字典序,只考虑代价最小。

明显的,我们有一个贪心的想法:确定先填奇数还是先填偶数之后,我们让奇数和偶数的相对位置关系不变,这种策略的代价一定是最小的。

(证明:我们考虑交换排序过后的两个数,分类讨论一下,可以得知答案一定不会比直接按照原来的顺序更优。)

对于连续的一段(这里只谈奇数或者偶数),如果他们在排序之后相对原来的位置都是向右移动,且移动之后最小的编号都不小于原来位置最大的编号,那么他们是可以随便安排位置的(消耗的代价: $\sum p_i-i$ ,是固定的)。因为要保证字典序最小,所以我们从小到大向里面放。

类似的,如果都是向左移动,且移动之后的最大编号都不大于原来的最小编号。我们就从大到小的且尽量靠右的放进去。

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
#include<bits/stdc++.h>
#define del(a,i) memset(a,i,sizeof(a))
#define ll long long
#define inl inline
#define il inl void
#define it inl int
#define ill inl ll
#define re register
#define ri re int
#define rl re ll
#define mid ((l+r)>>1)
#define lowbit(x) (x&(-x))
#define INF 0x3f3f3f3f
using namespace std;
template<class T>il read(T &x){
int f=1;char k=getchar();x=0;
for(;k>'9'||k<'0';k=getchar()) if(k=='-') f=-1;
for(;k>='0'&&k<='9';k=getchar()) x=(x<<3)+(x<<1)+k-'0';
x*=f;
}
template<class T>il _print(T x){
if(x/10) _print(x/10);
putchar(x%10+'0');
}
template<class T>il print(T x){
if(x<0) putchar('-'),x=-x;
_print(x);
}
ll mul(ll a,ll b,ll mod){long double c=1.;return (a*b-(ll)(c*a*b/mod)*mod)%mod;}
it qpow(int x,int m,int mod){
int res=1,bas=x%mod;
while(m){
if(m&1) res=(1ll*res*bas)%mod;
bas=(1ll*bas*bas)%mod,m>>=1;
}
return res%mod;
}
const int MAXN = 1e5+5;
int n,val[MAXN],ans1[MAXN],ans2[MAXN],cnt[2],p[MAXN],now,last;
ll c[2];
vector<int> vec[2];
priority_queue<int> q;
il solve(int ans[],int l,int r){ // 处理 l ~ r 这一段里面的排序问题
if(p[l]>l){ // 是向左调整的,先放大的,再放小的
now=r;
for(ri i=r;i>=l;i-=2){
while(now>=l&&p[now]>=i) q.push(val[p[now]]),now-=2;// 将所有的和 i 这个位置交换之后代价不变的点加入待选集合
ans[i]=q.top();q.pop();
}
} else{ // 是向右调整的,先放小的,再放大的
now=l;
for(ri i=l;i<=r;i+=2){
while(now<=r&&p[now]<=i) q.push(-val[p[now]]),now+=2;
ans[i]=-q.top();q.pop();
}
}
}
il build(int ans[],ll &cost,int ty){ // 以 ty 类型的开头
now=-1,last=1,cost=0; // last 为当前一段更改方向相同的区间的开头
for(ri i=1;i<=n;i+=2) p[i]=vec[ty][++now],cost+=abs(i-p[i]); // p 记录当前位置是由哪里转移过来的
now=-1;
for(ri i=2;i<=n;i+=2) p[i]=vec[ty^1][++now],cost+=abs(i-p[i]);
// 贪心,可以得到当奇数和偶数的相对位置不变时,一定是一个代价最小的解
for(ri i=1;i<=n;i+=2){
if(p[i]==i){
if(last==i) last+=2,ans[i]=val[i]; // 本身就是一个合法的位置,直接忽略掉
continue;
}
if((p[last]>last)!=(p[i]>i)) solve(ans,last,i-2),last=i; // 如果当前跳到了一个更改方向不同的点,就把前一段处理掉
}
solve(ans,last,(n&1)==(last&1)?n:n-1),last=2;// 将剩下的处理掉
for(ri i=2;i<=n;i+=2){
if(p[i]==i){
if(last==i) last+=2,ans[i]=val[i];
continue;
}
if((p[last]>last)!=(p[i]>i)) solve(ans,last,i-2),last=i;
}
solve(ans,last,(n&1)==(last&1)?n:n-1);
}
il Print(int ans[]){for(ri i=1;i<=n;++i) print(ans[i]),putchar(' ');}
int main(){
// freopen("seq2.ans","r",stdin);
// freopen("seq1.out","w",stdout);
read(n),c[0]=c[1]=LLONG_MAX;
for(ri i=1;i<=n;++i){
read(val[i]);
vec[val[i]&1].push_back(i);
++cnt[val[i]&1];
}
if(cnt[0]>=cnt[1]) build(ans1,c[0],0);
if(cnt[1]>=cnt[0]) build(ans2,c[1],1);
if(c[0]<c[1]) Print(ans1);// 如果先放奇数答案更优
else if(c[1]<c[0]) Print(ans2);// 如果先放数答案更优
else if(ans1[1]<ans2[1]) Print(ans1);// 如果先放奇数字典序更小
else Print(ans2);// 如果先放偶数字典序小
return 0;
}

$T2$

Solution

Code

$T3$

Solution

首先,我们需要明确一个事实:颁奖的集合有且只有一个

如果有多个,很容易用反证法推出矛盾。

设 $F_{n,k}$ 表示 $n$ 个人中,给 $k$ 个人颁奖的概率。

考虑转移。

设 $p=\frac{a}{b},q=1-p$ 。

如果不给第 $n+1$ 个人颁奖,那么他一定要输给在集合中的 $k$ 个人。因为这 $k$ 个人的编号一定比 $n+1$ 小,所以概率是 $p^k$ 。

如果要给第 $n+1$ 个人颁奖,那么他一定要赢前面的 $n-k+1$ 个人,概率为 $q^{n-k+1}$ 。(和在集合中的 $k$ 没有关系)

因此我们有 $F_{n+1,k}=F_{n,k}\cdot p^k+F_{n,k-1}\cdot q^{n-k+1}$ 。

这样我们就可以得到一个 $O(n^2)$ 的算法。

考虑优化。

我们发现在中间加入很困难,可以试着找两边的加入。因为右边已经考虑,所以考虑 $1$ 。

如果不给第 $1$ 个人颁奖,那么他一定要输给在集合中的 $k$ 个人。因为这 $k$ 个人的编号一定比 $1$ 大,所以概率是 $q^k$ 。

如果要给第 $1$ 个人颁奖,那么他一定要赢后面的 $n-k+1$ 个人,概率为 $p^{n-k+1}$ 。(和在集合中的 $k$ 没有关系)

因此我们又有 $F_{n+1,k}=F_{n,k}\cdot q^k+F_{n,k-1}\cdot p^{n-k+1}$ 。

和上面的式子合并一下,可以得到:

这样就可以 $O(n\log n)$ 完成计算。( $\log$ 是由于求逆元)

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
#include<bits/stdc++.h>
#define del(a,i) memset(a,i,sizeof(a))
#define ll long long
#define inl inline
#define il inl void
#define it inl int
#define ill inl ll
#define re register
#define ri re int
#define rl re ll
#define mid ((l+r)>>1)
#define lowbit(x) (x&(-x))
#define INF 0x3f3f3f3f
using namespace std;
template<class T>il read(T &x){
int f=1;char k=getchar();x=0;
for(;k>'9'||k<'0';k=getchar()) if(k=='-') f=-1;
for(;k>='0'&&k<='9';k=getchar()) x=(x<<3)+(x<<1)+k-'0';
x*=f;
}
template<class T>il _print(T x){
if(x/10) _print(x/10);
putchar(x%10+'0');
}
template<class T>il print(T x){
if(x<0) putchar('-'),x=-x;
_print(x);
}
ll mul(ll a,ll b,ll mod){long double c=1.;return (a*b-(ll)(c*a*b/mod)*mod)%mod;}
it qpow(int x,int m,int mod){
int res=1,bas=x%mod;
while(m){
if(m&1) res=(1ll*res*bas)%mod;
bas=(1ll*bas*bas)%mod,m>>=1;
}
return res%mod;
}
const int MAXN = 1e6+5,mod = 998244353,inv2 = 499122177;
int n,a,b,pow1[MAXN],pow2[MAXN],fac[MAXN],inv[MAXN],ans[MAXN],p,q;
it mul(int x,int y){return 1ll*x*y%mod;}
it add(int x,int y){return x+y>=mod?x+y-mod:x+y;}
it C(int n,int m){return mul(fac[n],mul(inv[m],inv[n-m]));}
int main(){
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
read(n),read(a),read(b);
if(a==b/2){
fac[0]=1,inv[0]=1;
for(ri i=1;i<=n;++i) fac[i]=mul(fac[i-1],i),inv[i]=qpow(fac[i],mod-2,mod);
for(ri i=1;i<n;++i) ans[i]=mul(C(n,i),qpow(inv2,1ll*i*(n-i)%(mod-1),mod));
ri res=0;
for(ri i=1,f=1;i<n;++i) res=add(res,mul(ans[i],f)),f=add(mul(f,f),2);
print(res);
return 0;
}
p=mul(a,qpow(b,mod-2,mod)),q=add(1,mod-p);
pow1[0]=pow2[0]=ans[0]=1;
for(ri i=1;i<=n;++i) pow1[i]=mul(pow1[i-1],p),pow2[i]=mul(pow2[i-1],q);
for(ri i=1;i<n;++i) ans[i]=mul(mul(ans[i-1],add(pow1[n-i+1],mod-pow2[n-i+1])),qpow(add(pow1[i],mod-pow2[i]),mod-2,mod));
ri res=0;
for(ri i=1,f=1;i<n;++i) res=add(res,mul(ans[i],f)),f=add(mul(f,f),2);
print(res);
return 0;
}

总结

还是不会分析题目,提炼出有用的性质。还有对于 $dp$ 还是不够熟练,特别是概率期望 $DP$ ,只要考到就懵逼,要加强练习。