2020_1_12
夜深人静,正是思考问题的好时间。
整理一下做过的题。
先是最近做的个人赛【2013南京现场赛】。
A
X Y 初始值都是1
然后每次增加X 会让Y增加Y/X (保留整数) 或者只增加Y
大的思路是一个贪心,可以根据条件推算出Y最终的表达式,那么我们可以贪心的想一定是在最前面去放Y是最优的。
记录floor 和 ceil
B
求就C(N,i)排列组合,抑或结果的求和
看到抑或,那么就需要思考二进制。
对于每次组合的求和结果,只有可能当相应的二进制位上的数字为奇数时,才可以产生贡献值。
所以我们对数字分解,然后对于每一个i都计算一次排列组合情况就可以了。
由于N只有1000所以O(n^2) 水过
C
再一次看错了题目,真就是觉得眼睛没用。
给出三种颜色的球的个数。
每次放球会有一个得分,放两侧那么得分是球的不同颜色数。
放球间,那么得分是放置位置前侧的不同颜色数+放置位置后侧的不同颜色数
然后暴力讨论就可以。
今晚的Atcoder
D
给出一个序列,然后问K个组合的(max-min)的和是多少。
数据量是100000
很容易就会想到可能的差值是哪些,也很好求,但是问题在与排列组合的方法。直接暴力,打表存不下,时间复杂度也会爆炸。
可以思考到,对于跨度为d的区间来说,可以产生的贡献值就是(max-min)* C(d-2, k-2)
C(d-2, k-2) 可以推算由上一次的值推算出来,需要乘法逆元。
(max - min) 可以用两次前缀和求出来。
E
是一个点的最小圆覆盖问题,一直以为直接模拟退火没毛病,但是今天被WA,只能老老实实的学几何的做法,再一看数据100000,果然模拟退火不是一个好办法。
顺便记录一下dalao代码里的好用的几何套路。
#include <stdio.h> #include <math.h> #include <algorithm> using namespace std; double sqr(double x) { return x * x; } class point { public: double x, y; point(double x, double y) : x(x), y(y){} point(){} // point(point &t) : x(t.x), y(t.y){} double len() { return sqrt(sqr(x) + sqr(y)); } point rotate() { return point(y, -x); } point operator - (point t) { return point(x-t.x, y-t.y); } point operator + (point t) { return point(x+t.x, y+t.y); } point operator / (int t) { return point(x/t, y/t); } point operator * (double t) { return point(x * t, y * t); } }a[100009], cir; point line_p(point p0, point v, point p1, point w) { double t = cross(p1 - p0, w) / cross(v, w); return p0 + v * t; } /* 记录一下自己艰险的手撸推算过程 谁能想到这个公式是化简的结果,谁能想到! (p0 + v * t - p1) * w = 0; (p0.x + v.x * t - p1.x, p0.y + v.y * t - p1.y) * (w.x, w.y) = 0; (p0.x + v.x * t - p1.x) * w.y = (p0.y + v.y * t - p1.y) * w.x p0.x * w.y + v.x * t * w.y - p1.x * w.y = p0.y * w.x + v.y * t * w.x - p1.y * w.x (v.x * w.y - v.y * w.x) * t = p0.y * w.x - p0.x * w.y + p1.x * w.y - p1.y * w.x (v.x * w.y - v.y * w.x) * t = (p0.y - p1.y) * w.x - (p0.x - p1.x) * w.y t = cross(p1 - p0, w) / cross(v, w); */ point circle(point a, point b, point c) { return line_p((a+b)/2, (b-a).rotate(), (a+c)/2, (c-a).rotate()); } int main() { int n; scanf("%d", &n); for(int i = 1; i <= n; i++) scanf("%lf %lf", &a[i].x, &a[i].y); random_shuffle(a+1, a+1+n); double r = 0; for(int i = 1; i <= n; i++) { if((a[i] - cir).len() > r) { cir = a[i]; r = 0; for(int j = 1; j < i; j++) { if((a[j] - cir).len() > r) { cir = (a[i] + a[j]) / 2; r = (cir - a[i]).len(); for(int k = 1; k < j; k++) { if((cir-a[k]).len() > r) { cir = circle(a[i], a[j], a[k]); r = (cir - a[i]).len(); } } } } } } printf("%.10lf\n%.10lf %.10lf\n", r, cir.x, cir.y); }
零散的题目
CF1272F
给出两个括号串,问最短的括号串使得给出的字符串都是他的子串,不需要连续。
Dalao的做法是dp[i][j][k] 真是想不到。
以下为dalao的思路
对于这个问题,我们可以分解为两个问题
1、最小的字符串使得给出的字符串被包含
2、这个字符串需要是合法的括号
到此豁然开朗,但是代码还是有点问题。第二个子问题的方程还是很好写的。
不是很明白第一个子问题的求解方法。
于是,引申出了一个问题。
对于两个只含小写字母的串,如何求出一个最短串来包含这两个串。
比如 abb 和 bbc 那么答案应该是abbc
在研究的了dalao的代码后,才得到了转移方程,然后记忆化路径就好了
#include <stdio.h> #include <cstring> #include <algorithm> using namespace std; int dp[1009][1009]; char s1[10009], s2[10009]; struct node { int x, y; char add; node(){} node(int x, int y, char add) : x(x), y(y), add(add){} }pre[1009][1009]; void path(int x, int y) { if(x==0 && y==0) return; path(pre[x][y].x, pre[x][y].y); putchar(pre[x][y].add); } int main() { int len1, len2; while(scanf("%s %s", s1+1, s2+1) != EOF) { len1 = strlen(s1+1); len2 = strlen(s2+1); for(int i = 0; i <= len1; i++) for(int j = 0; j <= len2; j++) dp[i][j] = 2e9; dp[0][0] = 0; for(int i = 0; i <= len1; i++) for(int j = 0; j <= len2; j++) for(char k = 'a'; k <= 'z'; k++) { int f1 = 0, f2 = 0; if(s1[i+1]==k) f1=1; if(s2[j+1]==k) f2=1; dp[i+f1][j+f2] = min(dp[i+f1][j+f2], dp[i][j]+1); if(dp[i+f1][j+f2]==dp[i][j]+1) { pre[i+f1][j+f2] = node(i, j, k); } } printf("%d\n", dp[len1][len2]); path(len1, len2); printf("\n"); } }
至此,这个问题才算完全搞明白了。