纪中DAY15做题小结
T1:淘汰赛制
Description
淘汰赛制是一种极其残酷的比赛制度。2 ^ n名选手分别标号1,2,3,……2 ^ n-1,2 ^ n,他们将要参加n轮的激烈角逐。每一轮中,将所有参加该轮的选手按标号从小到大排序后,第1位与第2位比赛,第3位与第4位比赛,第5位与第6位比赛……只有每场比赛的胜者才有机会参加下一轮的比赛(不会有平局)。这样,每轮将淘汰一半的选手。n轮过后,只剩下一名选手,该选手即为最终的冠军。
现在已知每位选手分别与其他选手比赛获胜的概率,请你预测一下谁夺冠的概率最大。
Input
输入文件第一行是一个整数n(1<=n<=10),表示总轮数。接下来2n行,每行2n个整数,第i行第j个是pij(0<=pij<=100,pii=0,pij+pji=100),表示第i号选手与第j号选手比赛获胜的概率。
Output
输出文件只有一个整数c,表示夺冠概率最大的选手编号(若有多位选手,输出编号最小者)。
Sample Input
2 0 90 50 50 10 0 10 10 50 90 0 50 50 90 50 0
Sample Output
1
Hint
【数据规模】
30%的数据满足n<=3
100%的数据满足n<=10
简要思路:这题其实就是一道普通的模拟题,只要表示好状态即可。我是用
n
u
m
[
i
]
[
j
]
num[i][j]
num[i][j]来表示
i
i
i和
j
j
j对战
i
i
i战胜
j
j
j的概率,用
f
[
i
]
[
j
]
f[i][j]
f[i][j]来表示
i
i
i在第
j
j
j轮获胜的概率。这里要注意一点,不能简单地将某一轮中获胜概率低的人排除掉,因为任何一人都会对最后的结果产生一定的影响。概率计算方法为
f
[
i
]
[
j
]
=
∑
k
∈
i
在
第
j
轮
的
对
手
f
[
k
]
[
j
−
1
]
∗
f
[
i
]
[
j
−
1
]
∗
n
u
m
[
i
]
[
k
]
f[i][j] =\sum_{k \in i在第j轮的对手} f[k][j - 1] * f[i][j - 1]*num[i][k]
f[i][j]=k∈i在第j轮的对手∑f[k][j−1]∗f[i][j−1]∗num[i][k],这里加法原理与乘法原理都用上了。
然后就是注意如何枚举i在第j轮的对手以及概率的初始化,这些就不讲了。
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
int n , end;
int num[1025][1025];
double f[1025][11];
inline void read( int & res ) {
res = 0;
int pd = 1;
char aa = getchar();
while ( aa < '0' || aa > '9' ) {
if ( aa == '-' ) {
pd = -pd;
}
aa = getchar();
}
while ( aa >= '0' && aa <= '9' ) {
res = ( res << 1 ) + ( res << 3 ) + ( aa - '0' );
aa = getchar();
}
res *= pd;
return;
}
int main () {
read(n);
end = 1 << n;
for ( int i = 1 ; i <= end ; ++i ) {
for ( int j = 1 ; j <= end ; ++j ) {
read(num[i][j]);
}
}
for ( int i = 1 ; i <= end ; ++i ) {
f[i][0] = 1.0;
}
for ( int j = 1 ; j <= n ; ++j ) {
for ( int i = 1 ; i <= end ; ++i ) {
int mi = 1 << (j - 1);
int tem = (i - 1) / mi;
if ( tem % 2 ) {
for ( int k = ( tem - 1 ) * mi + 1 ; k <= tem * mi ; ++k ) {
f[i][j] += f[i][j - 1] * f[k][j - 1] * ((double)(num[i][k]) / 100.00);//为了能用上快读,我宁愿强制转化(o(* ̄︶ ̄*)o)
}
} else {
for ( int k = ( tem + 1 ) * mi + 1 ; k <= ( tem + 2 ) * mi ; ++k ) {
f[i][j] += f[i][j - 1] * f[k][j - 1] * ((double)(num[i][k]) / 100.00);
}
}
//f[i][j] /= (double)mi;
//printf("%.3lf ",f[i][j]);
}
//printf("\n");
}
double maxn = 0;
int pos = 0;
for ( int i = 1 ; i <= end ; ++i ) {
if ( f[i][n] > maxn ) {
maxn = f[i][n];
pos = i;
}
}
printf("%d",pos);
return 0;
}
T2:方程的解
Description
佳佳碰到了一个难题,请你来帮忙解决。
对于不定方程a1+a2+……+ak-1+ak=g(x),其中k>=2且k∈N*,x是正整数,g(x)=x^x mod 1000(即xx除以1000的余数),x,k是给定的数。我们要求的是这个不定方程的正整数解组数。
举例来说,当k=3,x=2时,分别为(a1,a2,a3)=(2,1,1),(1,2,1),(1,1,2).
Input
输入文件有且只有一行,为用空格隔开的两个正整数,依次为k,x。
Output
输出文件有且只有一行,为方程的正整数解组数。
Sample Input
3 2
Sample Output
3
Data Constraint
【数据范围】 对于40%的数据,ans<= 1 0 16 10^{16} 1016; 对于100%的数据,k<=100,x<= 2 31 2^{31} 231-1,k<=g(x)。
简要思路:这道题就是求杨辉三角形第
g
(
x
)
g(x)
g(x)行的第k个,这是打表发现的 ,实现就自己想吧。
还有一个方法,就是用隔板插入小球空隙的方法,假设1就是这些一个一个的小球,将小球分成k份,要放k - 1个隔板,小球之间的空隙可放隔板,空隙共
g
(
x
)
−
1
g(x) - 1
g(x)−1个,不难得出排列组合的式子为
C
g
(
x
)
−
1
k
−
1
C^{k - 1}_{g(x) - 1}
Cg(x)−1k−1。
还有第三种方法,就是我在考场上想的,其实是最傻逼的一种 ,通过理(yi)性(bo)分(luan)析(gao),我发现当k = 2时,答案为
g
(
x
)
−
1
g(x) - 1
g(x)−1,当k = 3时,答案为
∑
i
=
1
g
(
x
)
−
2
i
\sum^{g(x)-2}_{i = 1}i
∑i=1g(x)−2i,当k = 4时,答案为
∑
j
=
1
g
(
x
)
−
3
∑
i
=
1
j
i
\sum^{g(x)-3}_{j = 1}\sum^{j}_{i = 1}i
∑j=1g(x)−3∑i=1ji,每当
k
k
k加上1,外面的
∑
\sum
∑符号就多一层,最外层循环限制为
g
(
x
)
−
k
+
1
g(x) - k + 1
g(x)−k+1,因此,只要全部赋初值为一,弄k-1次前缀和,输出
g
(
x
)
−
k
+
1
g(x) - k + 1
g(x)−k+1的前缀和即可。
不论用什么方法,都要用快速幂和高精,高精建议压一下位,既能优化一下又能掩盖自己蒟蒻的身份 。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#define mod 100000000
#define mo 1000
using namespace std;
struct node{
int n[55];
int siz;
friend node operator + ( node a , node b ) {
int len = max( a.siz , b.siz );
node res;
res.siz = len;
memset( res.n , 0 , sizeof(res.n) );
for ( int i = 1 ; i <= len ; ++i ) {
res.n[i] += a.n[i] + b.n[i];
res.n[i + 1] += res.n[i] / mod;
res.n[i] %= mod;
}
while ( res.n[res.siz + 1] ) {
res.siz++;
res.n[res.siz + 1] += res.n[res.siz] / mod;
res.n[res.siz] %= mod;
}
return res;
}
}num[1005];
int nn , k , x;
inline void read( int & res ) {
res = 0;
int pd = 1;
char aa = getchar();
while ( aa < '0' || aa > '9' ) {
if ( aa == '-' ) {
pd = -pd;
}
aa = getchar();
}
while ( aa >= '0' && aa <= '9' ) {
res = ( res << 1 ) + ( res << 3 ) + ( aa - '0' );
aa = getchar();
}
res *= pd;
return;
}
int qt( int a , int b ) {
a %= mo;
int res = 1;
while ( b ) {
if ( b & 1 ) {
res *= a;
res %= mo;
}
a *= a;
a %= mo;
b >>= 1;
}
return res;
}
int main () {
//freopen( "c.out" , "w" , stdout );
read(k);
read(x);
nn = qt( x , x );
//cout << nn << endl;
nn = nn - k + 1;
if ( nn <= 0 ) {
printf("0");
return 0;
}
for ( int i = 1 ; i <= nn ; ++i ) {
num[i].n[1] = 1;
num[i].siz = 1;
}
for ( int i = 1 ; i <= k - 1 ; ++i ) {
for ( int j = 1 ; j <= nn ; ++j ) {
//c();
num[j] = num[j - 1] + num[j];
}
}
printf("%d",num[nn].n[num[nn].siz]);
for ( int i = num[nn].siz - 1 ; i >= 1 ; --i ) {
printf("%08d",num[nn].n[i]);
}
return 0;
}
T3:物流运输
Description
物流公司要把一批货物从码头A运到码头B。由于货物量比较大,需要n天才能运完。货物运输过程中一般要转停好几个码头。物流公司通常会设计一条固定的运输路线,以便对整个运输过程实施严格的管理和跟踪。由于各种因素的存在,有的时候某个码头会无法装卸货物。这时候就必须修改运输路线,让货物能够按时到达目的地。但是修改路线是一件十分麻烦的事情,会带来额外的成本。因此物流公司希望能够订一个n天的运输计划,使得总成本尽可能地小。
Input
第一行是四个整数n( 1 <= n <= 100 )、m( 1 <= m<= 20 )、K和e。n表示货物运输所需天数,m表示码头总数,K表示每次修改运输路线所需成本。
接下来e行每行是一条航线描述,包括了三个整数,依次表示航线连接的两个码头编号以及航线长度(>0)。其中码头A编号为1,码头B编号为m。单位长度的运输费用为1。航线是双向的。再接下来一行是一个整数d,后面的d行每行是三个整数P(1 < P < m)、a、b(1 <= a <= b <= n)。表示编号为P的码头从第a天到第b天无法装卸货物(含头尾)。同一个码头有可能在多个时间段内不可用。但任何时间都存在至少一条从码头A到码头B的运输路线。
Output
包括了一个整数表示最小的总成本。总成本=n天运输路线长度之和+K*改变运输路线的次数。
Sample Input
5 5 10 8 1 2 1 1 3 3 1 4 2 2 3 2 2 4 4 3 4 1 3 5 2 4 5 2 4 2 2 3 3 1 1 3 3 3 4 4 5
Sample Output
32
Data Constraint
n( 1 <= n <= 100 )、m( 1 <= m<= 20 )
Hint
简要思路:这题在考场上我一点思路都没有,但是在考场下一分析就发现,这只是一个简单的图上DP啊,只是以前很少在图上DP所以没往这方面想(思维能力有待加强)。读入数据后,我们可以直接暴力判断不可用的点,对于状态,我们用 f [ i ] f[i] f[i]表示到第 i i i天的最少花费, d i s [ i ] [ j ] dis[i][j] dis[i][j]表示第 i i i天到第 j j j天去掉共同的不可用码头后从1到 m m m的最短路。初始状态定为 f [ i ] = i ∗ d i s [ 1 ] [ i ] f[i] = i * dis[1][i] f[i]=i∗dis[1][i],转移为 f [ i ] = m i n ( f [ i ] , f [ j ] + ( i − j ) ∗ d i s [ j + 1 ] [ i ] + k ) f[i] = min( f[i] , f[j] + ( i - j ) * dis[j + 1][i] + k ) f[i]=min(f[i],f[j]+(i−j)∗dis[j+1][i]+k),用SPFA预处理dis的值即可(floyrd好像会被卡)。
#include <iostream>
#include <cstdio>
#include <cstring>
#define N 105
#define M 25
#define ll long long//不知为何不开long long不放心
using namespace std;
int n , m , k , e , p , bcnt;
int head[N] , flag[M][N] , q[M * 2] , vis[M] , br[M];
ll d[M] , dis[N][N] , f[N];
struct node{
int next;
int to;
int val;
}str[M * M * 2];
inline void read( int & res ) {
res = 0;
int pd = 1;
char a = getchar();
while ( a < '0' || a > '9' ) {
if ( a == '-' ) {
pd = -pd;
}
a = getchar();
}
while ( a >= '0' && a <= '9' ) {
res = ( res << 1 ) + ( res << 3 ) + ( a - '0' );
a = getchar();
}
res *= pd;
return;
}
inline void insert( int from , int to , int val ) {
str[++bcnt].next = head[from];
head[from] = bcnt;
str[bcnt].to = to;
str[bcnt].val = val;
return;
}
inline ll spfa( int fr , int to ) {
//memset( d , 0x3f , sizeof(d) );long long用这方法赋出的值会非常大
for ( int i = 1 ; i <= m ; ++i ) {
d[i] = 1000000000;
}
for ( int i = 1 ; i <= m ; ++i ) {
br[i] = 0;
for ( int j = fr ; j <= to ; ++j ) {
if ( flag[i][j] ) {
br[i] = 1;//标记这几天不能用的点
break;
}
}
}
int l = 1 , r = 1;
q[1] = 1;
vis[1] = 1;
d[1] = 0;
while ( l <= r ) {
int cur = q[l];
vis[cur] = 0;
for ( int i = head[cur] ; i ; i = str[i].next ) {
int sn = str[i].to;
ll va = str[i].val;
if ( !br[sn] ) {
if ( d[sn] > d[cur] + va ) {
d[sn] = d[cur] + va;
if ( !vis[sn] ) {
q[++r] = sn;
vis[sn] = 1;
}
}
}
}
++l;
}
return d[m];
}
int main () {
read(n);
read(m);
read(k);
read(e);
int a , b , d;
for ( int i = 1 ; i <= e ; ++i ) {
read(a);
read(b);
read(d);
insert( a , b , d );
insert( b , a , d );
}
read(p);
for ( int i = 1 ; i <= p ; ++i ) {
read(d);
read(a);
read(b);
for ( int j = a ; j <= b ; ++j ) {
flag[d][j] = 1;
}
}
for ( int i = 1 ; i <= n ; ++i ) {
for ( int j = i ; j <= n ; ++j ) {
dis[i][j] = spfa( i , j );
//printf("%lld ",dis[i][j]);
}
//printf("\n");
}
for ( int i = 1 ; i <= n ; ++i ) {
f[i] = (ll)i * dis[1][i];
for ( int j = 1 ; j <= i - 1 ; ++j ) {
f[i] = min( f[i] , f[j] + ( i - j ) * dis[j + 1][i] + (ll)k );
}
}
printf("%lld",f[n]);
return 0;
}
T4:矩阵乘法(mat)
Description
给你一个 N*N 的矩阵,不用算矩阵乘法,但是每次询问一个子矩形的第 K 小数。
Input
第一行两个数 N,Q ,表示矩阵大小和询问组数;
接下来 N 行 N 列一共 N*N 个数,表示这个矩阵;
再接下来 Q 行每行 5 个数描述一个询问: x1,y1,x2,y2,k 表示找到以 (x1,y1) 为左上角、以 (x2,y2) 为右下角的子矩形中的第 K 小数。
Output
对于每组询问输出第 K 小的数。
Sample Input
2 2 2 1 3 4 1 2 1 2 1 1 1 2 2 3
Sample Output
1 3
Hint
矩阵中数字是 10 ^ 9 以内的非负整数;
20% 的数据: N<=100,Q<=1000 ;
40% 的数据: N<=300,Q<=10000 ;
60% 的数据: N<=400,Q<=30000 ;
100% 的数据: N<=500,Q<=60000 。
简要思路:这题主要是考一个整体二分,感觉这种思维方式非常精妙,通过不断地二分区间从而得出最后的答案。据说这题有在线与离线的做法,可是本蒟蒻只会离线 。这题的做法不方便用语言描述,具体实现请看代码,难懂的地方有注释。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 505
#define M 60005
#define lowbit(x) x&(-x)
using namespace std;
int n , q;
struct node{
int x;
int y;
int val;
}a[N * N];
struct no{
int x1;
int y1;
int x2;
int y2;
int k;
}b[M];
int pos[M] , tem1[M] , tem2[M] , ans[M] , tree[N][N] , siz[M];
inline void read( int & res ) {
res = 0;
int pd = 1;
char aa = getchar();
while ( aa < '0' || aa > '9' ) {
if ( aa == '-' ) {
pd = -pd;
}
aa = getchar();
}
while ( aa >= '0' && aa <= '9' ) {
res = ( res << 1 ) + ( res << 3 ) + ( aa - '0' );
aa = getchar();
}
res *= pd;
return;
}
inline void update( int xx , int yy , int v ) {
for ( int i = xx ; i <= n ; i += lowbit(i) ) {
for ( int j = yy ; j <= n ; j += lowbit(j) ) {
tree[i][j] += v;
}
}
}
inline int query( int xx , int yy ) {
int ans = 0;
for ( int i = xx ; i ; i -= lowbit(i) ) {
for ( int j = yy ; j ; j -= lowbit(j) ) {
ans += tree[i][j];
}
}
return ans;
}
inline int pcnt( int xx1 , int yy1 , int xx2 , int yy2 ) {
return query( xx2 , yy2 ) - query( xx2 , yy1 - 1 ) - query( xx1 - 1 , yy2 ) + query( xx1 - 1 , yy1 - 1 );
/*二维树状数组与二维差分,不会自学吧*/
}
inline bool cmp( node aa , node bb ) {
return aa.val < bb.val;
}
inline void solve( int l , int r , int ql , int qr ) {
if ( ql > qr ) {//当前区间没有询问
return;
}
if ( l == r ) {//范围已缩小成一个点,则有关询问可以得出答案
for ( int i = ql ; i <= qr ; ++i ) {
ans[pos[i]] = a[l].val;
}
return;
}
int mid = ( l + r ) >> 1;
int cnt1 = 0, cnt2 = 0;
int sum;
for ( int i = l ; i <= mid ; ++i ) {//因为siz已记录贡献,这里只用从l开始算
update( a[i].x , a[i].y , 1 );
}
for ( int i = ql ; i <= qr ; ++i ) {
sum = pcnt( b[pos[i]].x1 , b[pos[i]].y1 , b[pos[i]].x2 , b[pos[i]].y2 );
if ( siz[pos[i]] + sum >= b[pos[i]].k ) {//用siz数组记录小于mid的点的贡献
tem1[++cnt1] = pos[i];
} else {
siz[pos[i]] += sum;
tem2[++cnt2] = pos[i];
}
}
for ( int i = l ; i <= mid ; ++i ) {
update( a[i].x , a[i].y , -1 );
}
int cnt = ql - 1;
for ( int i = 1 ; i <= cnt1 ; ++i ) {
pos[++cnt] = tem1[i];//将询问通过归并的方式进行整合,分为已超越第k大和不满第k大两类
}
for ( int i = 1 ; i <= cnt2 ; ++i ) {
pos[++cnt] = tem2[i];
}
solve( l , mid , ql , ql + cnt1 - 1 );
solve( mid + 1 , r , ql + cnt1 , qr );
}
int main () {
read(n);
read(q);
int cnt = 0;
int te;
for ( int i = 1 ; i <= n ; ++i ) {
for ( int j = 1 ; j <= n ; ++j ) {
read(te);
a[++cnt].val = te;
a[cnt].x = i;
a[cnt].y = j;//二维降成一维
}
}
sort( a + 1 , a + 1 + cnt , cmp );
for ( int i = 1 ; i <= q ; ++i ) {
read(b[i].x1);
read(b[i].y1);
read(b[i].x2);
read(b[i].y2);
read(b[i].k);
pos[i] = i;
}
solve( 1 , cnt , 1 , q );
for ( int i = 1 ; i <= q ; ++i ) {
printf("%d\n",ans[i]);
}
return 0;
}
启示:
一:二分出奇迹;
二:一个OIER不仅要学会大量的算法,更应该改良自己的思维方式和提高思维能力。