这本来应该是一场AK的比赛,结果自己没把握住机会,有点遗憾。其实除了D是看了题解之后顿悟的,其他题目都不难,比赛时候写不出来,也只能说自己水平不够,因此写篇博客纪念。
题目链接:http://codeforces.ru/contest/340
A:
一直是水题的一道题,每次要抓紧时间,我现在最快也就4分钟A这题。。。英语实在捉急。。。题目就是求一个最小公倍数。然后区间减法。
代码:
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<cstdlib>
#include<queue>
#include<algorithm>
#include<stack>
#include<deque>
#include<list>
#include<set>
#include<vector>
#include<iomanip>
#include<cctype>
#include<string>
#include<memory>
#include<map>
#include<sstream>
#pragma warning (disable : 4996)
#define mem(a) memset(a, 0, sizeof(a))
#define sl(a) strlen(a)
typedef long long LL;
typedef double dou;
const int Mod = 1000000007;
const int N = 100005;
using namespace std;
LL gcd(LL a, LL b) {return a % b == 0 ? b : gcd(b, a % b);}
int main()
{
LL re, n, m, i, j, k, x, y, a, b, c;
cin >> x >> y >> a >> b;
c = x * y / gcd(x, y);
cout << (b) / c - (a - 1) / c << endl;
return 0;
}
B:
B是个几何问题,没A掉纯属自己太胆小,题目就是给一个300个点的集合,没有三点共线,就其中有最大面积的那个四边形的面积。(这里的四边形可以是凹的),第一眼看到就想把四边形沿对角线分成两个三角形,然后在对角线两边分别求最大三角形面积,但是当时自己粗略估计了一下时间复杂度,O(n^3),我还以为n范围是10^5就没敢做,后来虽然看到n是300,但一想,27000000可能也会TLE,这应该不是题解要的算法,结果比完才发现,题解的标准算法是O(n^3)的暴力搜索。只怪自己没胆量,顺便说一句,一般1秒能接受的运算次数是10^8,O(n^3)的算法一秒的话,n可以大至700左右(同学交流的经验)。既然是暴力代码实现就很简单了,三角形面积用叉乘来算,正负号则刚好说明了点在对角线的不同侧,注意这题一定要在两边都有点,否则取到的最大值并不是我们想要的结果。
代码:
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<cstdlib>
#include<queue>
#include<algorithm>
#include<stack>
#include<deque>
#include<list>
#include<set>
#include<vector>
#include<iomanip>
#include<cctype>
#include<string>
#include<memory>
#include<map>
#include<sstream>
#pragma warning (disable : 4996)
#define mem(a) memset(a, 0, sizeof(a))
#define sl(a) strlen(a)
typedef long long LL;
typedef double dou;
const int Mod = 1000000007;
const int N = 305;
using namespace std;
LL a[N], x[N], y[N];
int main()
{
LL re, n, m, i, j, k,u, d, s;
/*ios :: sync_with_stdio(false);*/
cin >> n;
for (i = 0; i < n; ++i) cin >> x[i] >> y[i];
for (i = 0, m = 0; i < n; ++i)
for (j = i + 1; j < n; ++j)
{
if (j != i)
{
for (k = 0, u = 0, d = 0; k < n; ++k)
if (k != i && k != j)
{
s = x[j] * y[k] + x[k]*y[i] + x[i] * y[j] - y[i] * x[j] - y[j] * x[k] - y[k] * x[i];
if (s < 0) d = max(abs(s), d);
else u = max(s, u);
}
if (u > 0 && d > 0)
m = m > (u + d) ? m : u + d;
}
}
printf("%.10lf\n", m / 2.0);
return 0;
}
C:
C是一道数学题,仔细分析一下,就能写出表达式,关键是表达式中有一项是这样的,求题中这条直线上所有点两两距离的和,这当然可以O(n^2),但是显然会TLE,稍微做分析就可以写出表达式是一个与n以及系数有关的表达式,可以在o(n)内解出,于是就水水过了。(Ps, 不知道有没有比O(n)还快的方法,欢迎评论,指点)。
代码:
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<cstdlib>
#include<queue>
#include<algorithm>
#include<stack>
#include<deque>
#include<list>
#include<set>
#include<vector>
#include<iomanip>
#include<cctype>
#include<string>
#include<memory>
#include<map>
#include<sstream>
#pragma warning (disable : 4996)
#define mem(a) memset(a, 0, sizeof(a))
#define sl(a) strlen(a)
typedef long long LL;
typedef double dou;
const int Mod = 1000000007;
const int N = 100005;
using namespace std;
LL a[N], s[N];
LL gcd(LL a, LL b) {return a % b == 0 ? b : gcd(b, a % b);}
int main()
{
LL re, n, m, i, j, k, sum = 0, up, down, d;
scanf("%I64d", &n);
for (i = 1; i <= n; ++i) scanf("%I64d", a + i);
sort(a + 1, a + 1 + n);
for (i = 1; i <= n; ++i) s[i] = s[i - 1] + a[i];
for (i = 1; i < n; ++i)
sum += (n - i) * a[n - i + 1] - (n - i) * a[i];
up = sum * 2 + s[n];
down = n;
d = gcd(up, down);
up /= d;
down /= d;
cout << up << ' ' << down << endl;
return 0;
}
D:
个人觉得D是一道好题,模拟冒泡排序过程然后结合了图论,我当时又有畏惧心理,没敢仔细想,其实题解中的Lemma1还是能想到的,至于Lemma2就觉得需要一些creation了,我是没想到。但是知道了以后也很简单了。题意就是冒泡排序过程(过程进行到所有数排好序)中,有逆序对,则在对应点之间连边。我们很容想明白的'是,原序列中所有逆序对的数之间肯定都连了边,然后题目要求的是最大的点集的元素个数,使得集合中任意两点间没有连边。这是什么意思呢?满足这个要求的集合是什么呢?Lemma2说的就是这个集合,这个集合的元素是元序列的最长上升子序列,知道结论后回过头看,我们可以证明:首先显然上升子序列在的任意两个元素,是不会连边的。对于某一个上升子序列,能不能添加其他元素而满足题目要求呢?不可能。我们任取一个元素不属于原来的那个上升子序列,为什么它不是这个序列中的元素呢?因为肯定有某个元素和他是逆序关系,不然的话直接添加到序列头或者尾显然能得到一个新的上升子序列。这样就证明了结论。题目中要求集合元素个数最多,当然就是求LIS问题啦。这是可以DP O(n^2)解决的,但是更快的是O(nlogn),具体实现需要二分查找,当然STL中有现成的lower_bound,为什么不用呢?
代码:
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<cstdlib>
#include<queue>
#include<algorithm>
#include<stack>
#include<deque>
#include<list>
#include<set>
#include<vector>
#include<iomanip>
#include<cctype>
#include<string>
#include<memory>
#include<map>
#include<sstream>
#pragma warning (disable : 4996)
#define mem(a) memset(a, 0, sizeof(a))
#define sl(a) strlen(a)
typedef long long LL;
typedef double dou;
const int Mod = 1000000007;
const int N = 100005;
using namespace std;
LL a[N], b[N];
int main()
{
LL re, n, m, i, j, k, en, y;
ios :: sync_with_stdio(false);
cin >> n;
for (i = 0; i < n; ++i) cin >> a[i];
b[0] = a[0];
for (i = 1, en = 1; i < n; ++i)
{
y = lower_bound(b, b + en, a[i]) - b;
b[y] = a[i];
if (y == en) en++;
}
cout << en << endl;
return 0;
}
顺便说一句LISnlogn算法不仅可以求长度,加些改进可以求出一组符合长度最长的上升子序列。这题需要再吃透的就是如何自己发现问题的本质是LIS,这是需要自己不断学习,总结,最后加上一些创造性的。
E:
E是经典的错排,唯一不同就是给定了其中几个点的位置,但仍然可以用容斥远离解决,蛋疼菊紧的是比赛时写的容斥原理是用DFS实现的,也怪pretest太水,没把我的代码跳出来,结果最后测试TLE了(主要受以前一道数论题影响,其实那种容斥原理只适合数据规模很小的情况,但在数论中每个数的素因子个数非常少,整型数据,不同素因子个数肯定不超过20个,所以很适用)这题数据大至2000,用DFS实现,时间复杂度是O(2^n)显然TLE,其实很容易写出容斥原理的公式,最后O(n)直接求解,设k是所有当前状态下可能使 a[i] == i 成立的 i 的个数,F 是所有还没被填上去的数的个数, 那么答案就是:
代码:
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<cstdlib>
#include<queue>
#include<algorithm>
#include<stack>
#include<deque>
#include<list>
#include<set>
#include<vector>
#include<iomanip>
#include<cctype>
#include<string>
#include<memory>
#include<map>
#include<sstream>
#pragma warning (disable : 4996)
#define mem(a) memset(a, 0, sizeof(a))
#define sl(a) strlen(a)
typedef long long LL;
typedef double dou;
const int Mod = 1000000007;
const int N = 2005;
using namespace std;
LL a[N], s[N], dead[N], c[N] = {1};
LL fac[N] = {1, 1};
// 原来写的DFS TLE T……T
//LL dfs(LL cnt, LL x, LL y)
//{
// int i, j, k, ans = 0;
// if (x == 0) return 1;
// ans = (ans + fac[x]) % Mod;
// for (i = y; i < cnt; ++i)
// if (s[re[i]] == 0)
// {
// ans = (ans - dfs(cnt, x - 1, i + 1)) % Mod;
// if (ans < 0) ans += Mod;
// }
// return ans;
//}
LL qp(LL a, LL b)
{
LL re =1;
while (b)
{
if (b & 1) re = re * a % Mod;
b >>= 1;
a = a * a % Mod;
}
return re;
}
int main()
{
LL n, m, i, j, k, cnt, num = 0, re = 0;
for (i = 2; i <= N; ++i) fac[i] = fac[i - 1] * i % Mod;
cin >> n;
for (i = 1, cnt = 0; i <= n; ++i)
{
scanf("%I64d", a + i);
if (a[i] == -1) cnt++;
else dead[a[i]] = 1, s[i] = 1;
}
for (i = 1, k = 0; i <= n; ++i) if (dead[i] == 0 && s[i] == 0) k++;
for (i = 1; i <= k; ++i) c[i] = (c[i - 1] * (k - i + 1)) % Mod * qp(i, Mod - 2) % Mod;
for (i = 0; i <= k; ++i)
{
if (i & 1)
{
re = (re - fac[cnt - i] * c[i] % Mod) % Mod;
if (re < 0) re += Mod;
}
else
re = (re + fac[cnt - i] * c[i] % Mod) % Mod;
}
cout << re <<endl;
return 0;
}
E貌似比赛时很多人DP做的,不甚了解如何实现,有想法请评论,指教。
我还是滚回去再反思一下D吧。。。怎么样发现问题的本质是自己需要提高的地方。