ATCoder code festival 2016 qual C


[AtCoder] code festival 2016 qual C

说在前面

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

官方题解链接:
Editorial

做题借鉴了两位神仙的题解:

A CF 终究是ATCoder变了心

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

这个题大水 直接放代码

#include
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)\)
注:减一是因为第一天并不计算。

//代码的变量名和题目不一样
#include
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序列单调不增。(都是从左往右)

很显然我们可以发现一个性质,如果\(T_i\)\(T_{i+1}\)不同,第\(i + 1\)个山的高度是已知的(\(A\)同理)。于是我们可以推断出所有已知的山高,而其余未知的地方,山的高度\(h_i\)可能是\(\forall h_i \in [1, \min(T_i,A_i)]\),答案直接乘上就可以了。

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)\)满足

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

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

我们发现,相邻两列的代价与其他两列无关,比如1、2列的代价并不会影响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
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;
}

至于相邻两列代价与其他列无关的问题,我一开始也有疑问,而网上找到的题解都没有详细解释。我自己尝试证明感性理解了一波。可能出锅。
我们首先考虑只有1、2两列的情况,这两列之间会有一个最优解。再考虑加进来第3列,2、3列之间也会有一个最优解。这两个最优解为何不会冲突呢?
因为他们的关系只是这两列之间的相对关系,就拿第三个样例的前三列来说。
1、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

而2、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列,再操作1、2,并不会对结果产生影响。
以此类推,第\(i\)列与第\(i + 1\)列的操作都是相对的,因此相邻并没有影响,\(DP\)结果直接相加即可。

E 順列辞書

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

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

真的锅了
感谢@nofindnofind神仙的博客

设该排列的第\(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;
}