noip再刷题
解方程 用了秦九昭算法
f(x)=0,f(x%p)=0,缩小数据范围的思想,就像以前一到考对数的题,p最好取可靠的质数。
天天爱跑步 树上差分,一次性统计
运输计划 树上差分
飞扬的小鸟 第一次做是完全背包和01背包顺序弄反,这一次做的时候更新有问题,只枚举了下界到上界的更新,在完全背包的时候必须从一开始,而不能从下界开始(思考为什么)
蚯蚓 数学证明,两个优先队列,对于在变化的增量, 我们可以在入队时提前减去贡献,就不用考虑取出时的变化,最后的三个队列不能sort,复杂度会爆
斗地主 只是一张或两张牌的情况不应该搜
逛公园 最短路,计划化搜索,对于从终点还可以走回去的情况,新建一个最终原点
宝藏 一道很好的状压
花匠 我用的线段树dp,实际上可以贪心
华容道 其实分的最短路还是不错了
https://www.cnblogs.com/EdSheeran/p/8718577.html
forgetten one:以上两道是二分图匹配,有一个经典模型最小路径覆盖;
二分图最大匹配=最小点覆盖=总点数-最小点独立集;
大致证明:
最大匹配=最小点覆盖:如果还能匹配,那就证明还有边没被覆盖。
最小点覆盖=n-最大点独立集:如果独立集可以增大k,那就证明有k个点间两两没边,那最小点覆盖就可以删去这k个点。N个点,去掉最小点覆盖的,剩下的两两没边,就是最大点独立集。
最小边覆盖=n-最大匹配:本来是n条边(每个点要被一条边覆盖),每匹配一对,就相当于减少了一条边。
最小路径覆盖:最坏情况是一个点一个覆盖,每覆盖一次路径数就减少1,尽可能匹配多的边,每匹配一次相当于融合一次点;
相互攻击:找最小点覆盖,我们希望删去一些点,这些点攻击的人很多,又要使这些点尽量少,所以就是最小点覆盖;
拆开考虑的数位DP,存了一个sum,表示s在当前数中出现的次数,到底就统计sum的贡献,加上这一维是因为他<=数位
画图得到经典性质:树+最多一个环,如果没有环就是裸上司的舞会;
有环拆成树,强制不走换上的边,从两边各做一次强制祖先不选的舞会,此题还要开longlong,又忘了;
安慰奶牛cheer 我可能是个傻逼,一道黄题想不出来;
最小生成树:关键是把点权划归在边权上,边走两次,点会对每条边进行贡献;
The Captain 寒假最后一测原题, dijistra的板子; 题意求:边权min(|xi-xj|, |y1-yj|),求起点到终点的最短路;
https://www.cnblogs.com/EdSheeran/p/8496929.html
Eat the Trees 轮廓线DP裸题;(不是思维,难在代码)
每次轮廓线的代码是最难的,轮廓线设计的方式很重要,决定了代码的难度,这题的巧妙之处在于直接把轮廓线对应的位置进行翻转:
#include<bits/stdc++.h> using namespace std; #define ex(i, u) for(int i = h[u]; i; i = G[i].nxt) #define ll long long const int M = (1<<12) + 5; ll dp[13][13][M]; bool mp[13][13]; int read(){ int x=0,f=1;char c=getchar(); while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();} while(c<='9'&&c>='0'){x=x*10+c-'0';c=getchar();} return x*=f; } int main(){ //freopen("cc.out","w",stdout); int T, idc = 0; scanf("%d", &T); while(T--){ int n, m; scanf("%d%d", &n, &m); memset(dp, 0, sizeof(dp)); for(int i=1; i<=n; i++) for(int j=1; j<=m; j++) scanf("%d", &mp[i][j]); dp[0][m][0] = 1; for(int i = 1; i <= n; i++){ /*for(int s = 0; s < 1<<(1+m); s++) dp[i][0][s>>1] = dp[i-1][m][s]; */ for(int s = 0; s < (1<<m); s++) dp[i][0][s<<1] = dp[i-1][m][s]; for(int j = 1; j <= m; j++) for(int s = 0; s < 1<<(1+m); s++){ int p1 = 1<<(j-1), p2 = 1<<j; if(mp[i][j]){ if((p1&s) && (p2&s)) dp[i][j][s] = dp[i][j-1][s^p1^p2]; else if(!(p1&s) && !(p2&s)) dp[i][j][s] = dp[i][j-1][s^p1^p2]; else dp[i][j][s] = dp[i][j-1][s] + dp[i][j-1][s^p1^p2]; } else{ if(!(p1&s) && !(p2&s)) dp[i][j][s] = dp[i][j-1][s]; } //if(dp[i][j][s])printf("%d %d %d %lld ",i, j, s, dp[i][j][s]); } } printf("Case %d: There are %I64d ways to eat the trees. ", ++idc, dp[n][m][0]); } }
两双手 思维DP;
排序+容斥
为什么排序后不考虑从后面回来呢? 由于坐标化简后相当于只向右上走,所以按坐标排序,只有排在它之前的点可能到达它
假面舞会: 一道探究性质的图论好题
https://blog.csdn.net/sunshinezff/article/details/48628401
两个难点:对环和链性质的分类讨论,建立-1的边保证找到环的起点和终点;
绝世好题
https://vjudge.net/contest/263626#problem/A
给定一个长度为n的数列ai,求ai的子序列bi的最长长度,满足bi&bi-1!=0(2<=i<=len)。
n<=100000,ai<=2*10^9
题解:很明显是一个拆位建边的题,但这样肯定TT;
可以直接DP,我上次没有深刻理解,这次理解了,但自己实现的时候还是思考不够深刻;
单独一位直接往下扫就好了,但很多时候边都是交错的,我自以为解决了这个问题,其实不然;
很简单的例子:2 3 1;
把WA和AC的对比一下;
WA
#include<bits/stdc++.h> using namespace std; const int M = 1e5 + 5; struct node{int w, t;}q[M]; int a[M], f[M]; int main(){ int n; scanf("%d", &n); for(int i = 1; i <= n; i++)scanf("%d", &a[i]), f[i] = 1; int ans = 0; for(int i = 0; i <= 31; i++){ int lst = 0; for(int j = 1; j <= n; j++){ if(a[j] & (1LL<<i)){ f[j] = max(f[j], f[lst]+1); if(f[j] > f[lst]) lst = j; } } } for(int i = 1; i <= n; i++) ans = max(ans, f[i]); printf("%d ", ans); }
AC
#include<bits/stdc++.h> using namespace std; int f[33]; int main(){ int n; scanf("%d", &n); for(int i = 1; i <= n; i++){ int mx = 0, x; scanf("%d", &x); for(int i = 0; i <= 31; i++) if(x & (1<<i)) mx = max(mx, f[i]); for(int i = 0; i <= 31; i++) if(x & (1<<i))f[i] = max(f[i], mx + 1); } int ans = 0; for(int i = 0; i <= 31; i++) ans = max(ans, f[i]); printf("%d ", ans); }
D - BLO
这题是原题,是对tarjan点双联通的一个运行过程的理解,一道好题;
可以参看以前写的:https://www.cnblogs.com/EdSheeran/p/9029346.html
Check the difficulty of problems
http://poj.org/problem?id=2151
一道概率DP,以前写过,但现在一点思路都没有,概率期望问题很大,而且因为玄学问题WA了一上午;
题意: ACM比赛中,共M道题,T个队,pij表示第i队解出第j题的概率 问 每队至少解出一题且冠军队至少解出N道题的概率。 解析:DP 设dp[i][j][k]表示第i个队在前j道题中解出k道的概率 则: dp[i][j][k]=dp[i][j-1][k-1]*p[j][k]+dp[i][j-1][k]*(1-p[j][k]); 先初始化算出dp[i][0][0]和dp[i][j][0]; 设s[i][k]表示第i队做出的题小于等于k的概率 则s[i][k]=dp[i][M][0]+dp[i][M][1]+``````+dp[i][M][k]; 则每个队至少做出一道题概率为P1=(1-s[1][0])*(1-s[2][0])*```(1-s[T][0]); 每个队做出的题数都在1~N-1的概率为P2=(s[1][N-1]-s[1][0])*(s[2][N-1]-s[2][0])*```(s[T][N-1]-s[T][0]); 最后的答案就是P1-P2
#include<stdio.h> #include<string.h> #include<algorithm> #include<iostream> #include<math.h> using namespace std; double dp[1010][50][50]; double s[1010][50]; double p[1010][50]; int main() { int M,N,T; while(scanf("%d%d%d",&M,&T,&N)!=EOF) { if(M==0&&T==0&&N==0)break; for(int i=1;i<=T;i++) for(int j=1;j<=M;j++) scanf("%lf",&p[i][j]); for(int i=1;i<=T;i++) { dp[i][0][0]=1; for(int j=1;j<=M;j++)dp[i][j][0]=dp[i][j-1][0]*(1-p[i][j]); for(int j=1;j<=M;j++) for(int k=1;k<=j;k++) dp[i][j][k]=dp[i][j-1][k-1]*p[i][j]+dp[i][j-1][k]*(1-p[i][j]); s[i][0]=dp[i][M][0]; for(int k=1;k<=M;k++)s[i][k]=s[i][k-1]+dp[i][M][k]; } double P1=1; double P2=1; for(int i=1;i<=T;i++) { P1*=(1-s[i][0]); P2*=(s[i][N-1]-s[i][0]); } printf("%.3lf ",P1-P2); } return 0; }