纪中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]=kijf[k][j1]f[i][j1]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)1k1
还有第三种方法,就是我在考场上想的,其实是最傻逼的一种 ,通过理(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)3i=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
图1

简要思路:这题在考场上我一点思路都没有,但是在考场下一分析就发现,这只是一个简单的图上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]=idis[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]+(ij)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不仅要学会大量的算法,更应该改良自己的思维方式和提高思维能力。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值