ATCoder code festival 2016 qual C

[AtCoder] code festival 2016 qual C

说在前面

在洛谷水题看到了\(A\)题, 而后就花了一个晚自习把这个比赛作了\(2333\)
原题目连接:
AtCoder
在洛谷题号为AT2084 ~ AT2088
这篇博客前两题是洛谷的翻译,后三道本身翻译的可能有锅\(2333\)
已在洛谷提交翻译等待审核html

官方题解连接:
Editorialc++

作题借鉴了两位神仙的题解:
http://www.javashuo.com/article/p-clvnluag-q.html
http://www.javashuo.com/article/p-zmwnnpiq-hy.html数组

A CF 终究是ATCoder变了心

问一个字符串S(2<=S<=100)中是否能够去掉几个字符,变成CF(大写)两个字母,若是能够,输出‘Yes’,不能够,输出‘No’。post

这个题大水 直接放代码spa

#include<bits/stdc++.h>
using namespace std;
char s[200];
int main()
{
	cin >> s + 1;
	int len = strlen(s + 1);
	bool c,f;
	c = f = 0;
	for(register int i = 1; i <= len; ++i)
	{
		if(s[i] == 'C')
			c = 1;
		if(s[i] == 'F' && c)
			f = 1;
	}
	puts(c && f ? "Yes" : "No");
	return 0;
}

B

某人有K块蛋糕,他想天天吃一块,正好K天吃完。
这K块蛋糕共分为T种,第i种蛋糕有a[i]个。
这我的不想连续两天吃一样种类的蛋糕,他想知道本身最少有多少天本身吃的蛋糕和前一天吃的蛋糕种类同样。翻译

咱们考虑如何才能尽量的让他不吃到一样的蛋糕。必定是相同蛋糕之间有其余蛋糕隔开。所以出现次数最多的蛋糕必定能够用来分割其余的蛋糕。而若是有蛋糕相邻,也必定是出现次数最多的蛋糕已经将其他蛋糕彻底分割依旧有剩余。
设最多的蛋糕有n块。
答案就是:\(max(n - 1 - (k - n), 0)\)
注:减一是由于第一天并不计算。code

//代码的变量名和题目不同
#include<bits/stdc++.h>
using namespace std;
int main()
{
	int n, T;
	cin >> n >> T;
	int mx = 0;
	for(register int i = 1; i <= T; ++i)
	{
		int x;
		cin >> x;
		mx = max(mx, x);
	}
	cout << max(mx - 1 - (n - mx), 0) << endl;
}

C 二人のアルピニスト

高橋君和青木君去徒步爬过了一个著名的山脉。这个山脉由不少个山东西走向依次排开,高橋君从东往西爬,青木君从西往东爬。他们只能记住本身到目前为止通过的最高的山是多高。高橋君记录下的是\(T_i\),青木君记下的是\(A_i\)
询问山高的可能序列的个数,答案对\(1e9 + 7\)取模。
若是给出的序列不合法输出\(0\)
一句话题意:告诉你一个序列前缀最大值和后缀最大值,求这个序列的可能个数。
范围:
\(1 \leq N \leq 1e5\)
$1 \leq A_i \leq 1e9 $
$1 \leq T_i \leq 1e9 $
保证T序列单调不降, A序列单调不增。(都是从左往右)htm

很显然咱们能够发现一个性质,若是\(T_i\)\(T_{i+1}\)不一样,第\(i + 1\)个山的高度是已知的(\(A\)同理)。因而咱们能够推断出全部已知的山高,而其他未知的地方,山的高度\(h_i\)多是\(\forall h_i \in [1, \min(T_i,A_i)]\),答案直接乘上就能够了。blog

const int MAXN = 1e5 + 10;
const int MOD = 1e9 + 7;
int n;
int T[MAXN], A[MAXN];
int  h[MAXN];
signed main()
{
	poread(n);
	for(register int i = 1; i <= n; ++i)
		poread(T[i]);
	for(register int i = 1; i <= n; ++i)
		poread(A[i]);
	for(register int i = 1; i <= n; ++i)
		if(T[i] != T[i - 1])
			h[i] = T[i];
	for(register int i = n; i >= 1; --i)
		if(A[i] != A[i + 1])
			h[i] = A[i];
	for(register int i = 1, mx = 0; i <= n; ++i)
	{
		mx = max(h[i], mx);
		if(mx != T[i])
		{
			puts("0");
			return 0;
		}
	}
	for(register int i = n, mx = 0; i >= 1; --i)
	{
		mx = max(h[i], mx);
		if(mx != A[i])
		{
			puts("0");
			return 0;
		}
	}
	int ans = 1;
	for(register int i = 1; i <= n; ++i)
	{
		if(!h[i])
		{
			ans = (long long) ans * min(A[i],T[i]) % MOD;
		}
	}
	cerr << ans << endl;
	cout << ans << endl;
}

D Friction

高橋君有一个\(h * w\)的艺术品,艺术品的每一块都有一个小写字母表示颜色。
高橋君想把这个艺术品拆掉,他采起这样的方式:
选取一列并把这一列向下推一格,这样这一列最下边的颜色会消失。
可是这会产生代价。产生的代价是选择的这一列中,与相邻格子颜色相同的格子数。确切来讲,当有一对格子\((p,q)\)知足ci

  • \(p\)在所选的这一列
  • \(p,q\)相邻
  • \(p,q\)颜色相同

这对格子会产生\(1\)的代价。
请计算高橋君把这个艺术品彻底拆除所须要的最小代价。
范围:
\(1 \leq h \leq 300\)
\(2 \leq w \leq 300\)

咱们发现,相邻两列的代价与其余两列无关,好比一、2列的代价并不会影响二、3列的代价。所以\(DP\)计算相邻两列的计划。
\(f[x][y]\)表示第一列往下推了\(x\)格, 第二列往下推了\(y\)格的最小代价。
转移显然 \(f[x][y] = \min(f[x-1][y] + cost[x-1][y],f[x][y-1] + cost[x][y-1] )\)
\(cost\)可使用前缀和计算,节省一维的时间复杂度。
每次计算相邻两列便可。时间复杂度\(O(h^2 \cdot w)\)

#include<bits/stdc++.h>
using namespace std;
const int MAXN =  305;
int n, m;
char th[MAXN][MAXN];
inline int calc(const int &x)
{
	static int f[MAXN][MAXN];
	for(register int i = 1; i <= n; ++i)
		for(register int j = 1; j <= n; ++j)
			f[i][j] = (th[i][x] == th[j][x + 1]);
	for(register int i = 1; i <= n; ++i)
		for(register int j = 1; j <= n; ++j)
			f[i][j] = f[i - 1][j - 1] + f[i][j];
	for(register int i = 1; i <= n; ++i)
		for(register int j = 1; j <= n; ++j)
			f[i][j] += min(f[i - 1][j], f[i][j - 1]);
	return f[n][n];
}
int main()
{
	scanf("%d %d", &n, &m);
	for(register int i = 1; i <= n; ++i)
		scanf("%s", th[i] + 1);	
	register int ans = 0;
	for(register int i = 1; i < m; ++i)
		ans += calc(i);
	cout << ans << endl;
}

至于相邻两列代价与其余列无关的问题,我一开始也有疑问,而网上找到的题解都没有详细解释。我本身尝试证实感性理解了一波。可能出锅。
咱们首先考虑只有一、2两列的状况,这两列之间会有一个最优解。再考虑加进来第3列,二、3列之间也会有一个最优解。这两个最优解为什么不会冲突呢?
由于他们的关系只是这两列之间的相对关系,就拿第三个样例的前三列来讲。
一、2列的最优解应该是:第一列向下两次,第二列向下一次,以后每列轮换向下移动,最小代价为4。
不画图了表格写一会儿,本身手玩。

1 2 3 4
aa a a
ab ab b a
ab ab ab ab
ab ab ab ab
aa aa aa ab

而二、3列之间应该是将第3列一直推到底,总代价为8。

1 2 3 4 5 6
aa a a a a a
bb ba b b b b
ba bb ba b b b
bb ba bb ba b b
aa ab aa ab aa a

这两列合到一块儿呢?
实际能够先操做第3列,再操做一、2,并不会对结果产生影响。
以此类推,第\(i\)列与第\(i + 1\)列的操做都是相对的,所以相邻并无影响,\(DP\)结果直接相加便可。

E 順列辞書

对于一个长为\(n\)的序列,它的全排列有\(n!\)种。
高橋君有一本记录着长为\(n\)的序列的全排列、有\(n!\)页的序列词典,词典的第\(i\)页记录着排名为\(i\)的排列。
高橋君想要查找一个序列,但他忘了其中的几个数。因此他要查询全部可能记录这个序列的页数。
如今告诉你他还记得的部分序列,他忘记的地方是\(0\),请你告诉他,他全部要查询的页码之和,答案对\(1e9 + 7\)取模。
一句话题意:给一个不全的排列,求出全部可能的排列的排名之和。
范围:\(1 \leq n \leq 5e5\)

这道题上网找题解可是题解解释都不怎么详细,本身看了半天才懂。\(2333\)
如下解释可能出锅。

真的锅了
感谢@nofind@jiqimao神仙指出
已修正

前置知识:康托展开
推荐nofind神仙的博客

设该排列的第\(i\)项是\(a_i\)
先考虑康托展开。
\(\sum\limits_{i = 1}^{n} A_i \cdot (n - i)!\)
其中 \(A_i =a_i - \sum\limits_{j = 1}^{i}[a_j \leq a_i]\)
\((n - i)\)能够很快计算,咱们先考虑如何计算全部可能排列的\(A_i\)之和。
\(m\)是未知数的个数。易知可能的排列有\(m!\)种。
分状况讨论:\(a_i\)已知和\(a_i\)未知。

  • \(a_i\)已知时,若\(a_j\)已知,咱们能够利用树状数组快速计算\(A_i\),而且在全部的可能排列中,这个贡献不变。所以这一项的贡献就是
    \(\sum\limits_{j=1}^{i}[a_j < a_i] * m!\)
    同时计算第\(i\)位前面和后面未知的数与\(a_i\)所能产生的贡献。
    \(i\)位后面的数\(a_k\)产生贡献当且仅当\(a_k\)小于\(a_i\),所以所产生的贡献就是任选一个大于\(a_i\)的未知数填在后面的任意一位,其他数任意排列。
    \(\sum\limits_{k = 1}^{n}[a_k > a_i \&\&a_k\)未知\(] * (m - 1)!\) \(*\) 后边未知的位置数。
    \(i\)位前面的数\(a_j\)产生贡献分析同上。
    \(\sum\limits_{j=1}^{n}[a_j < a_i \&\& a_j\)未知\(] * (m - 1)!\) \(*\) 前面未知的位置数。
  • \(a_i\)未知时,\(a_j\)已知的状况已经包含在上面,考虑\(a_j\)未知的状况。
    此时的方案即是从\(m\)个未知数中任意挑选两个、其余的位置数任填的方案,而能产生的贡献的位置即是所剩下的未填数的位置。
    \(cnt\)表示已经计算贡献的未知位置数。
    贡献为\(C_{m}^{2} * (m - 2)! * (m - cnt)\)

以上式子,没有计算\((n - i)!\),程序里算上了有些不一样。
因为是排名的和,记得最后加上\(m!\)
上边式子中要预处理一些东西,这个影响就不大了看代码吧。

signed main()
{
	poread(n);
	fac[0] = 1;
	for(register int i = 1; i <= n; ++i)
		fac[i] = (long long)fac[i - 1] * i % MOD;
	int m = n;
	for(register int i = 1; i <= n; ++i)
	{
		poread(a[i]);
		if(a[i])
			++s[a[i]], --m;
	}
	for(register int i = 1; i <= n; ++i)
		suf[i] = s[i] = 1 - s[i], s[i] += s[i - 1];
	for(register int i = n; i >= 1; --i)
		suf[i] += suf[i + 1];
	register int sum = 0, cnt = 0;
	register int res = 0;
	for(register int i = n; i >= 1; --i)
	{
		if(a[i])
		{
			sum = (long long)ask(a[i] - 1) * fac[m] % MOD;
			if(cnt)
				sum = (sum + (long long)cnt * s[a[i]] % MOD * fac[m - 1] % MOD) % MOD;
			res = (res + (long long)sum * fac[n - i] % MOD) % MOD;
			add(a[i], 1);
		}
		else
		{
			++cnt;
		}
	}
	cnt = sum = 0;
	for(register int i = 1; i <= n; ++i)
	{
		if(a[i])
		{
			if(cnt)
				res = (res + (long long)sum * suf[a[i]] % MOD * fac[m - 1] % MOD) % MOD;
		}
		else
		{
			sum = (sum + fac[n - i]) % MOD;
			++cnt;
		}
	}
	sum =  (long long)(m * (m - 1) >> 1 ) % MOD, cnt = 0;
	if(m >= 2)	
		for(register int i = 1; i <= n; ++i)
		{
			if(!a[i])
			{
				++cnt;
				res = (res + (long long)sum * (m - cnt) % MOD * fac[m - 2] % MOD * fac[n - i] % MOD);
			}
		}
	res = (res + fac[m]) % MOD;
	cout << res << endl;
}
相关文章
相关标签/搜索