狂欢2
- T1:冒泡排序
- 题目
- 题解
- CODE
- T2:概率充电器
- 题目
- 题解
- CODE
- T3:不勤劳的图书管理员
- 题目
- 题解
- CODE
我不这么认为。。。。
T1:冒泡排序
题目
下面是一段实现冒泡排序算法的 C++代码:
for(int i=1; i<n; i++)for(int j=1; j<=n-i; j++)if(a[j]>a[j+1])swap(a[j],a[j+1]);
其中待排序的a数组是一个1,2…n的排列,swap 函数将交换数组中对应位置的值。
对于给定的数组 a 以及给定的非负整数k ,使用这段代码执行了正好k次 swap 操作之后数组 a 中元素的值会是什么样的呢?
题解
首先我们要了解冒泡排序的原理,举个例子
3 | 4 | 2 | 1 |
---|
首先3会与4进行比较,不交换
接着4与2比较,交换
4往后移了一位,j下标也+1,所以j还是映射的是4,与1比较交换
外层第一轮i循环后的结果如下
3 | 2 | 1 | 4 |
---|
3与2比较,交换
j+1,3也右移了一位,j仍映射着3,与1比较交换
接着与4比较,小于4中断,把接力棒给4,4是最后一位停止交换
外层第二轮i循环后的结果如下
2 | 1 | 3 | 4 |
---|
接下来,让我们用理论来推导这个过程的一些规律
1.外层循环在第i次结束后,对应的第i大的数就会出现在自己应该在的位置,并且[n−i+1,n][n-i+1,n][n−i+1,n]这一区间是严格不下降序列(此题中是严格上升),👉每一次交换是成单调不下降的
换言之,即x会一直与右边交换直到遇到一个比x大的y,然后x停下,y又与右边交换直到遇到与x相似的情况…
2.对于每一个x,最多只会发生一次左移,因为它只会有一次与左边的数进行比较,但是对于一个x可能发生多次右移,但x发生多少次右移就代表着有多少个数发生了左移,👉对于x,它左移的次数为左边比它大的数的个数,因为那些比它大的数必须要越过x才能到达自己应该在的位置
最后如果x左边没有比它大的数后,它就会固定在这个位置,因为没有数会越过它了
3.我们只考虑x左边比它大的数,因为如果x要右移的话就意味着有其他的数会左移,把其他数左移后留下来的位置就是x的位置啦!
考虑用树状数组去求[1,i−1][1,i-1][1,i−1]区间比a[i]a[i]a[i]大的个数
然后用二分去枚举外层循环的i,看能最多跑满多少次外层循环
剩下的就是跑不满的,那我们就暴力转移一次即可
CODE
#include <queue>
#include <cstdio>
#include <iostream>
using namespace std;
#define LL long long
#define MAXN 1000005
priority_queue < int > q;
int n, ans;
int a[MAXN], result[MAXN];
LL k;
int maxx[MAXN], tree[MAXN];int lowbit ( int x ) {return x & ( -x );
}LL query ( int x ) {LL tot = 0;for ( int i = x;i >= 1;i -= lowbit ( i ) )tot += tree[i];return tot;
}void add ( int x ) {for ( int i = x;i <= n;i += lowbit ( i ) )tree[i] ++;
}LL check ( int x ) {LL tot = 0;for ( int i = 1;i <= n;i ++ )tot += min ( x, maxx[i] );return tot;
}void solve ( int l, int r ) {if ( l > r )return;int mid = ( l + r ) >> 1;if ( check ( mid ) <= k ) {ans = mid;solve ( mid + 1, r );}elsesolve ( l, mid - 1 );
}int main() {scanf ( "%d %lld", &n, &k );for ( int i = 1;i <= n;i ++ ) {scanf ( "%d", &a[i] );maxx[i] = query ( n ) - query ( a[i] );add ( a[i] );}LL sum = 0;for ( int i = 1;i <= n;i ++ )sum += maxx[i];if ( sum < k )return ! printf ( "Impossible!" );solve ( 1, n - 1 );for ( int i = 1;i <= n;i ++ )if ( maxx[i] >= ans )result[i - ans] = a[i];elseq.push ( a[i] );for ( int i = n;i >= 1;i -- )if ( ! result[i] ) {result[i] = q.top();q.pop();if ( q.empty() )break;}k -= check ( ans );for ( int i = 1;i < n;i ++ )if ( result[i] > result[i + 1] ) {if ( ! k )break;swap ( result[i], result[i + 1] );k --;}for ( int i = 1;i <= n;i ++ )printf ( "%d ", result[i] );return 0;
}
T2:概率充电器
题目
Luogu走一波
著名的电子产品品牌SHOI 刚刚发布了引领世界潮流的下一代电子产品—— 概率充电器:
“采用全新纳米级加工技术,实现元件与导线能否通电完全由真随机数决 定!SHOI 概率充电器,您生活不可或缺的必需品!能充上电吗?现在就试试看 吧!”
SHOI 概率充电器由n-1 条导线连通了n 个充电元件。进行充电时,每条导 线是否可以导电以概率决定,每一个充电元件自身是否直接进行充电也由概率 决定。随后电能可以从直接充电的元件经过通电的导线使得其他充电元件进行 间接充电。
作为SHOI 公司的忠实客户,你无法抑制自己购买SHOI 产品的冲动。在排 了一个星期的长队之后终于入手了最新型号的SHOI 概率充电器。你迫不及待 地将SHOI 概率充电器插入电源——这时你突然想知道,进入充电状态的元件 个数的期望是多少呢?
输入格式
第一行一个整数:n概率充电器的充电元件个数。充电元件由1-n 编号。
之后的n-1 行每行三个整数a, b, p,描述了一根导线连接了编号为a 和b 的 充电元件,通电概率为p%。
第n+2 行n 个整数:qi。表示i 号元件直接充电的概率为qi%。
输出格式
输出一行一个实数,为能进入充电状态的元件个数的期望,四舍五入到小 数点后6 位小数。
输入输出样例
输入
3
1 2 50
1 3 50
50 0 0
输出
1.000000
输入
5
1 2 90
1 3 80
1 4 70
1 5 60
100 10 20 30 40
输出
4.300000
说明/提示
对于30%的数据,n≤5000
对于100%的数据,n≤500000,0≤p,qi≤100
题解
又是期望,哎😔,先引入一些关于概率的计算,我用通俗易懂的文字帮助大家理解,不用去搞那么死板的事件我不会告诉你因为我也不是很清楚
事件A与事件B同时发生的概率=事件A发生的概率*事件B发生的概率
事件A和事件B只其中一件发生的概率=事件A发生的概率+事件B发生的概率-事件AB同时发生的概率
(可以不用看, 反正下面我不用)
设dp[u]dp[u]dp[u]表示uuu号充电器亮的期望
那么我们思考转移方程式,如果uuu要亮,只需要任意一个儿子或者父亲给它供电,所以uuu点亮的期望等于自己本身亮加上父亲点亮加上任何一个儿子点亮的概率和,即
dp[u]=∏(v∈son[u])dp[v]∗pu,v+q[u]+dp[fa]∗pu,fadp[u]=\prod(v∈son[u])dp[v]*p_{u,v} +q[u]+dp[fa]*p_{u,fa}dp[u]=∏(v∈son[u])dp[v]∗pu,v+q[u]+dp[fa]∗pu,fa
但是这么想,其实是错误的,因为这个转移方程式的前提是fafafa与uuu的这一条充电线是好的,但是当我们用uuu在往上去转移fafafa的时候,又不能保证这一条充电线是好的,并且这个转移方程式中各个儿子间会重复,可能同时多个儿子供电的事件发生
因此我们反着来,设dp[u]dp[u]dp[u]表示uuu号充电器不亮的期望
那么转移方程式也是类似的,如果uuu不亮,要所有儿子和父亲都不给它供电,又有父亲,算了我们先不管父亲,重新定义dp[u]dp[u]dp[u]表示uuu号充电器不亮的期望,且只考虑u的儿子不给它供电导致的
dp[u]=∏(v∈son[u])(1−(1−dp[v])∗pu,v)∗(1−q[u])dp[u]=\prod(v∈son[u])(1-(1-dp[v])*p_{u,v})*(1-q[u])dp[u]=∏(v∈son[u])(1−(1−dp[v])∗pu,v)∗(1−q[u])
分条来理解含义,(1−dp[v])(1-dp[v])(1−dp[v])儿子vvv亮的概率,pu,vp_{u,v}pu,v这条充电线是好的,相乘就是儿子vvv给uuu供电的概率,再用1−()1-()1−()表示儿子不供电的概率,
为什么是相乘呢?因为所有儿子都不给它供电才能使得uuu不亮,所以这个事件是要同时发生的,并且要建立在uuu本身不亮的基础上,所以是相乘,结果还是用到了之前的铺垫打脸了
接下来我们必须去面对父亲了,设f[u]f[u]f[u]表示uuu号充电器不亮的概率,且是只考虑父亲不给它供电导致的
我们知道父子关系是相互的,uuu是fafafa的一个儿子,相反fafafa也是uuu的唯一父亲,
所以dp[fa]dp[fa]dp[fa]的更新会有uuu的参与,那么我们现在又是考虑fafafa给uuu供电,
就必须把uuu给fafafa供电的概率给去掉,
fafafa给uuu供电的概率且不考虑uuu,是由fafafa的父亲或者fafafa的其它儿子供应的
转移方程式就出来了:
f[u]=1−(1−(dp[fa]∗f[fa]/(1−(1−dp[u])∗faw)))∗fawf[u]=1 - ( 1 - ( dp[fa] * f[fa] / ( 1 - ( 1 - dp[u] ) * fa_w ) ) ) * fa_wf[u]=1−(1−(dp[fa]∗f[fa]/(1−(1−dp[u])∗faw)))∗faw
再分条理解:
dp[fa]∗f[fa]dp[fa] * f[fa]dp[fa]∗f[fa]则是父亲不亮的期望(包含有父亲的父亲和父亲的其他儿子给父亲供电的两种情况)
(dp[fa]∗f[fa]/(1−(1−dp[u])∗faw)( dp[fa] * f[fa] / ( 1 - ( 1 - dp[u] ) * fa_w )(dp[fa]∗f[fa]/(1−(1−dp[u])∗faw)则是排除掉uuu给fafafa供电的概率
1−(dp[fa]∗f[fa]/(1−(1−dp[u])∗faw))1-( dp[fa] * f[fa] / ( 1 - ( 1 - dp[u] ) * fa_w ) )1−(dp[fa]∗f[fa]/(1−(1−dp[u])∗faw))则是表示fafafa亮的期望,排除掉uuu供电使父亲亮起来的概率
(1−(dp[fa]∗f[fa]/(1−(1−dp[u])∗faw)))∗faw( 1 - ( dp[fa] * f[fa] / ( 1 - ( 1 - dp[u] ) * fa_w ) ) ) * fa_w(1−(dp[fa]∗f[fa]/(1−(1−dp[u])∗faw)))∗faw表示fafafa与uuu相连的充电线是好的,相乘则表示父亲让uuu亮的概率
最后减掉就是不亮的概率了
最后把f[u]f[u]f[u]和dp[u]dp[u]dp[u]相乘就表示没有任何一个亲戚能给该点供电,就是不亮的期望,最后把每个点用
1-不亮期望加起来就是亮起来的期望即答案
精度问题就定义一个极小值即可。。。
CODE
#include <cmath>
#include <cstdio>
#include <vector>
using namespace std;
const int eps = 1e-8;
#define MAXN 1000005
struct node {int v;double w;node ( int V, double W ) {v = V;w = W;}
};
vector < node > G[MAXN];
int n;
double result;
double dp[MAXN], f[MAXN];void dfs1 ( int u, int fa ) {f[u] = 1;dp[u] = 1 - dp[u];for ( int i = 0;i < G[u].size();i ++ ) {int v = G[u][i].v;double w = G[u][i].w;if ( v == fa )continue;dfs1 ( v, u );dp[u] *= ( 1 - ( 1 - dp[v] ) * w );}
}void dfs2 ( int u, int fa, double fa_w ) {if ( fabs ( 1 - ( 1 - dp[u] ) * fa_w ) > eps ) //维护精度 f[u] = 1 - ( 1 - ( dp[fa] * f[fa] / ( 1 - ( 1 - dp[u] ) * fa_w ) ) ) * fa_w;elsef[u] = 1 - fa_w;result -= dp[u] * f[u];for ( int i = 0;i < G[u].size();i ++ ) {int v = G[u][i].v;if ( v == fa )continue;dfs2 ( v, u, G[u][i].w );}
}
int main() {scanf ( "%d", &n );for ( int i = 1;i < n;i ++ ) {int a, b;double w;scanf ( "%d %d %lf", &a, &b, &w );G[a].push_back( node ( b, w / 100 ) );G[b].push_back( node ( a, w / 100 ) );}for ( int i = 1;i <= n;i ++ ) {scanf ( "%lf", &dp[i] );dp[i] /= 100;}result = n;dfs1 ( 1, 0 );dfs2 ( 1, 0, 0 );printf ( "%.6f", result );return 0;
}
T3:不勤劳的图书管理员
题目
luogu链接
加里敦大学有个帝国图书馆,小豆是图书馆阅览室的一个书籍管理员。他的任务是把书排成有序的,所以无序的书让他产生厌烦,两本乱序的书会让小豆产生这两本书页数的和的厌烦度。现在有n本被打乱顺序的书,在接下来m天中每天都会因为读者的阅览导致书籍顺序改变位置。因为小豆被要求在接下来的m天中至少要整理一次图书。小豆想知道,如果他前i天不去整理,第i天他的厌烦度是多少,这样他好选择厌烦度最小的那天去整理。
输入格式
第一行会有两个数,n,m分别表示有n本书,m天
接下来n行,每行两个数,ai和vi,分别表示第i本书本来应该放在ai的位置,这本书有vi页,保证不会有放置同一个位置的书
接下来m行,每行两个数,xj和yj,表示在第j天的第xj本书会和第yj本书会因为读者阅读交换位置
输出格式
一共m行,每行一个数,第i行表示前i天不去整理,第i天小豆的厌烦度,因为这个数可能很大,所以将结果模10^9 +7后输出
输入输出样例
输入
5 5
1 1
2 2
3 3
4 4
5 5
1 5
1 5
2 4
5 3
1 3
输出
42
0
18
28
48
说明/提示
对于20%的数据,1 ≤ ai; xj; yj ≤ n ≤ 5000, m ≤ 5000, vi ≤ 10^5
对于100%的数据,1 ≤ ai; xj; yj ≤ n ≤ 50000, m ≤ 50000, vi ≤ 10^5
题解
这道题与动态逆序对是类似的,也是一道树套树板题
lll与rrr的交换只会影响[l+1,r−1][l+1,r-1][l+1,r−1]区间,lll与rrr我们单独判断
1.原本xxx的优先级比lll小的在交换后会与lll形成逆序对,厌烦度则是这些xxx的页数和加上满足条件的xxx的个数乘以lll的页数
2.原本xxx的优先级比lll大的在交换后会与lll不再形成逆序对,要减去它的厌烦度,厌烦度则是这些xxx的页数和加上满足条件的xxx的个数乘以lll的页数
3.原本xxx的优先级比rrr小的在交换后会与rrr形成逆序对,厌烦度则是这些xxx的页数和加上满足条件的xxx的个数乘以rrr的页数
4.原本xxx的优先级比rrr大的在交换后会与rrr不再形成逆序对,减去厌烦度,厌烦度则是这些xxx的页数和加上满足条件的xxx的个数乘以rrr的页数
因为线段树套平衡树和线段树套权值线段树都要T,所以我被迫选择树状数组套线段树
CODE
#include <cstdio>
#include <iostream>
using namespace std;
#define LL long long
#define mod 1000000007
#define MAXN 500005
struct node {int l, r;LL v, cnt;
}tree[MAXN * 100];
int n, m, Size;
int root[MAXN * 100];
//注意开log级或者大一点,不然就会TLE/RE
LL ans;
LL a[MAXN], v[MAXN];
/*内层线段树*/
void modify ( int &t, int l, int r, int id, LL w1, LL w2 ) {if ( ! t )t = ++ Size;tree[t].v = ( tree[t].v + w1 ) % mod;tree[t].cnt = ( tree[t].cnt + w2 ) % mod;if ( l == r )return; int mid = ( l + r ) >> 1;if ( id <= mid )modify ( tree[t].l, l, mid, id, w1, w2 );elsemodify ( tree[t].r, mid + 1, r, id, w1, w2 );
}void query ( int &t, int l, int r, int L, int R, LL &s1, LL &s2 ) {if ( ! t )return;if ( L <= l && r <= R ) {s1 = ( s1 + tree[t].v ) % mod;s2 = ( s2 + tree[t].cnt ) % mod;return;}int mid = ( l + r ) >> 1;if ( L <= mid )query ( tree[t].l, l, mid, L, R, s1, s2 );if ( mid < R )query ( tree[t].r, mid + 1, r, L, R, s1, s2 );
}
/*外层树状数组*/
int lowbit ( int x ) {return x & ( -x );
}void Modify ( int x, int id, LL w1, LL w2 ) {for ( int i = x;i <= n;i += lowbit ( i ) )modify ( root[i], 1, n, id, w1, w2 );
}void Query ( int l, int r, int L, int R, LL &s1, LL &s2 ) {if ( L > R )return;LL w1 = 0, w2 = 0;//[l,r]=[1,r]-[1,l-1]for ( int i = r;i >= 1;i -= lowbit ( i ) )query ( root[i], 1, n, L, R, s1, s2 );for ( int i = l - 1;i >= 1;i -= lowbit ( i ) )query ( root[i], 1, n, L, R, w1, w2 );s1 = ( s1 - w1 + mod ) % mod;s2 = ( s2 - w2 + mod ) % mod;
}
//以下标作为外层树状数组,以优先级作为内层线段树(树状数组套线段树)
int main () {scanf ( "%d %d", &n, &m );for ( int i = 1;i <= n;i ++ )scanf ( "%lld %lld", &a[i], &v[i] );for ( int i = 1;i <= n;i ++ ) {LL s1 = 0, s2 = 0;Modify ( i, a[i], v[i], 1 );Query ( 1, i - 1, a[i] + 1, n, s1, s2 );ans = ( ans + s1 + v[i] * s2 % mod ) % mod;}for ( int i = 1;i <= m;i ++ ) {int l, r;scanf ( "%d %d", &l, &r );if ( l == r ) {printf ( "%lld\n", ans );continue;}if ( l > r )swap ( l, r );//l和r进行交换,只会对(l,r)区间带来影响 LL s1 = 0, s2 = 0, w1 = 0, w2 = 0;Query ( l + 1, r - 1, a[l] + 1, n, s1, s2 );//原本比l优先级高的在l交换后就形成了逆序对 Query ( l + 1, r - 1, 1, a[l] - 1, w1, w2 );//原本比l优先级低的在交换后就不再形成逆序对 ans = ( ans + s1 - w1 + mod ) % mod;ans = ( ans + s2 * v[l] % mod - w2 * v[l] % mod + mod ) % mod;//注意l和r的情况刚好是相反的 s1 = s2 = w1 = w2 = 0;Query ( l + 1, r - 1, 1, a[r] - 1, s1, s2 );//原本比r优先级低的在交换后就形成了逆序对 Query ( l + 1, r - 1, a[r] + 1, n, w1, w2 );//原本比r优先级高的在交换后就不再形成逆序对 ans = ( ans + s1 - w1 + mod ) % mod;ans = ( ans + s2 * v[r] % mod - w2 * v[r] % mod + mod ) % mod;//单独考虑l和r之间的关系 if ( a[l] < a[r] )ans = ( ans + v[l] + v[r] ) % mod;if ( a[l] > a[r] )ans = ( ans - v[l] - v[r] + mod ) % mod;//很巧妙,如果要删掉某一个数的信息,就传mod-(...),这样相加再取模就刚好消成了0实现了删除操作 Modify ( l, a[l], mod - v[l], mod - 1 );Modify ( r, a[r], mod - v[r], mod - 1 );Modify ( l, a[r], v[r], 1 );Modify ( r, a[l], v[l], 1 );swap ( a[l], a[r] );swap ( v[l], v[r] );printf ( "%lld\n", ans );}return 0;
}
有什么问题欢迎评论,byebye