前言
国庆假时的比赛大多已经记不清了,记得这次比赛第一题想复杂了,然后发挥就失常了,看来第一题不要想太多啊。!!。
Karen与游戏
题目描述
输入格式
输出格式
输入样例
输入样例1
3 5
2 2 2 3 2
0 0 0 1 0
1 1 1 2 1
输入样例2
3 3
0 0 0
0 1 0
0 0 0
输入样例3
3 3
1 1 1
1 1 1
1 1 1
输出样例
输出样例1
4
row 1
row 1
col 4
row 3
输出样例2
-1
输出样例3
3
row 1
row 2
row 3
题解(贪心)
这题我想复杂了,一开始当成最小割,于是码完了发现图错了,改了半天无果就搁下了。其实这就是个明显的贪心,假如一行要消一次肯定消最小的那个,然后列也一样。不过如果行多先消列,列多先消行。
看来我的最小割贪心简直弱爆了。
代码
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <cmath>
#include <algorithm>
#define maxn 105
#define oo 0x7fffffff
using namespace std;
int n, m, ans, g[maxn][maxn];
int row[maxn], col[maxn];
void DelR(){
for(int i = 1; i <= n; i++){
int minv = oo;
for(int j = 1; j <= m; j++)
minv = min(minv, g[i][j]);
for(int j = 1; j <= m; j++)
g[i][j] -= minv;
row[i] += minv;
}
}
void DelC(){
for(int j = 1; j <= m; j++){
int minv = oo;
for(int i = 1; i <= n; i++)
minv = min(minv, g[i][j]);
for(int i = 1; i <= n; i++)
g[i][j] -= minv;
col[j] += minv;
}
}
int main(){
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i++)
for(int j = 1; j <= m; j++)
scanf("%d", &g[i][j]);
if(n <= m){
DelR();
DelC();
}
else{
DelC();
DelR();
}
for(int i = 1; i <= n; i++)
for(int j = 1; j <= m; j++){
if(g[i][j]){
puts("-1");
return 0;
}
}
for(int i = 1; i <= n; i++) ans += row[i];
for(int i = 1; i <= m; i++) ans += col[i];
printf("%d\n", ans);
for(int i = 1; i <= n; i++){
while(row[i]){
printf("row %d\n", i);
row[i] --;
}
}
for(int i = 1; i <= m; i++){
while(col[i]){
printf("col %d\n", i);
col[i] --;
}
}
return 0;
}
puzzle
题目描述
输入格式
输出格式
输入样例
输入样例1
7
1 2 1 1 4 4
输入样例2
12
1 1 2 2 4 4 3 3 1 10 8
输出样例
输出样例1
1.0 4.0 5.0 3.5 4.5 5.0 5.0
输出样例2
1.0 5.0 5.5 6.5 7.5 8.0 8.0 7.0 7.5 6.5 7.5 8.0
题解(树形DP+期望)
发现期望真的超级重要的!去年的noip考了期望,今年可能也在上面动刀。
这题我其实是找规律的,找到规律后就简单证明了一下发现是对的。题目求期望的dfs序,假如确定了根的dfs序期望为Exp[x],明显儿子的dfs序跟各个儿子的子树大小有关。何时搜到儿子v取决v被x访问的顺序,这个顺序是随机的,我们有一个序列,就是看看v在序列的哪个位置,期望的Exp[v]就是它前面那些兄弟的子树大小和+1+Exp[x]。我们期望一下它前面的子树大小和。
根据期望是均值的理论,想想觉得v应期望出现在序列的中间,就是说它前面的节点应是总节点的一半。于是Exp[v] = Exp[x] + (f[x] - f[v] - 1) / 2.0。其中f[x]代表x子树大小。于是这样就是对的。
另外的理解就是相当于选一个点v,将其撒在这个序列里,学过各种概型就很容易知道其他的点在他前后的概率是一样的,就是对于u,v随机撒,明显u在v前面的概率是1/2(类比几何概型,虽然这是古典。。)。然后每个点能贡献的就是1/2,乘上值就是期望了。这样更加好懂为什么是 (f[x] - f[v] - 1) / 2.0了吧。
严谨的证明也是有的,不过考试时一些题的证明是比较困难的,这是就要大胆去试了,慢慢看吧。
代码
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <cstdlib>
#define maxn 100010
using namespace std;
int n, cur = -1;
int head_p[maxn], f[maxn];
double Exp[maxn];
struct Adj{int obj, next;} Edg[maxn];
void Insert(int a, int b){
cur ++;
Edg[cur].next = head_p[a];
Edg[cur].obj = b;
head_p[a] = cur;
}
void dfs1(int x){
f[x] = 1;
for(int i = head_p[x]; ~ i; i = Edg[i].next){
int v = Edg[i].obj;
dfs1(v);
f[x] += f[v];
}
}
void dfs2(int x){
for(int i = head_p[x]; ~ i; i = Edg[i].next){
int v = Edg[i].obj;
Exp[v] = 1 + Exp[x] + (f[x] - f[v] - 1) / 2.0;
dfs2(v);
}
}
int main(){
scanf("%d", &n);
for(int i = 1; i <= n; i++) head_p[i] = -1;
for(int i = 1, f; i < n; i++){
scanf("%d", &f);
Insert(f, i+1);
}
Exp[1] = 1.0;
dfs1(1);
dfs2(1);
for(int i = 1; i <= n; i++)
printf("%.1lf ", Exp[i]);
printf("\n");
return 0;
}
Karen与测试
题解(找规律+组合数学+逆元)
这题是最难的,因为它需要找规律。像我这种数学不好,编程找规律能力又差,高斯消元什么的也不会的就只能水分了。
赛后这题的题解看到我要呕血,十分丧病。后来我自己换了种方法,看看样例一发现一个神奇的东西,这个东西不是判断+-的搞来搞去烦到爆炸,而直接可以用优美的组合数学搞定一切。
36=3∗1+9∗2+15∗1
而1 2 1就是杨辉三角,我们由此发现当n%4==1时,系数竟然是杨辉三角!!例如等于9时是1 4 6 4 1(死算一遍),就是说偶数项的那些被算来算去消掉了,于是我们就可以直接用 O(nlog(1e7)) 的方法搞,一边求组合数,然后费马小定理求逆元。一开始强制做到n%4==1就行了。
正经的说法就是第 i∗2+1 个数被取的次数是 Cin2 ,而贡献都是正的。由于底数一定,于是O(n)递推+逆元搞定。不过i=0的时候逆元会搞到C变成0,我这里特殊跳过了。
这种找规律的题还是要用程序多试几组数据,然后就找到规律了。(这种题就是试探题,数列求和真是够数玄学的)
代码
#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <cstring>
#include <cmath>
#include <algorithm>
#define maxn 200010
#define MOD 1000000007
using namespace std;
typedef long long LL;
int n, m;
int a[maxn];
LL ans;
bool sign = true;
LL Pow(int x, int y){
LL res = 1LL;
while(y){
if(y & 1) res = 1LL * res * x % MOD;
x = 1LL * x * x % MOD;
y >>= 1;
}
return res;
}
int main(){
scanf("%d", &n);
for(int i = 1; i <= n; i++) scanf("%d", &a[i]);
while(n % 4 != 1){
for(int i = 1; i < n; i++){
if(sign) a[i] = (a[i] + a[i+1]) % MOD;
else a[i] = (a[i] - a[i+1] + MOD) % MOD;
sign ^= 1;
}
n --;
}
LL C = 1LL;
int p = n >> 1;
for(int i = 0; i <= p; i++){
if(i) C = C * (p - i + 1) % MOD * Pow(i, MOD-2LL) % MOD;
ans = (ans + C * a[i<<1|1] % MOD) % MOD;
}
printf("%lld\n", ans);
return 0;
}
总结
第一题挂了,找规律的题目做的不够多,思维好差,数学也好差,看来数学课一定要好好听啊!
只有弱者才喜欢扎堆,问题是绝大部分人都是弱者。