题目链接:https://vjudge.net/contest/155219#overview。
A题,用全排列来找出比当前这个数字字典序还大的排列有几个,然后前缀和dp即可。据说可以康拓展开来快速找出前面需要实现的要求。
B题,水题。
C题,感觉数据比较水。做法是dsu+lca,但是为了实现lca树的结构不被破坏,dsu::find()不能压缩路径。然后线性找lca没T也是有点神奇。
D题,dfs即可。
E题,dp[i][j][k],表示到了(i,j)并且已经吃了k个2,最多吃了几个3的状态。最后遍历一下dp[n][m][...]找出答案即可。
F题,水题。
G题,二分答案,然后跑dij即可。很久没写dij,刚开始竟然忘了优先队列要写一个greater..
H题,不会= =。
I题,水题。
J题,刚开始想了一个比较麻烦的方法,不需要考虑dfs的时候当前步是谁的做法,然后WA了。然后加上就可以了,还好写很多。不用记忆化搜索也能够过。
K题,dp[i][j]表示计算到i位,%p答案是j的种类数。然后滚动数组一下再一位一位dp即可。之所以dp[pre][0]每次都需要先+1是因为,之前是空串也算是一个0,换言之,只有当前这位数字%p是0也是可以的。
L题,这个博弈论构造起来好麻烦(虽然最终代码很简单)。。首先注意到随着个数的增加,一堆是必胜堆还是必败堆是交替变化的。必胜态考虑完了要考虑必败态的话,必败态的L肯定是上一个必胜态的R+1。然后因为必败态的任意一种选择都是必胜态,因此必败态的最大堆是上一个必胜态的R,然后其他堆都是1,如此不能再多了(因为再多一个,放在最大堆上,最大堆上就变成了必败态,这和必败态的定义不符合),因此这个必败态的R是是上一个必胜态的R+(n-1)。那么再考虑下一个必胜态,必胜态的定义是只要有一种方法能到达必败态即可,因此最大堆是上一个必败态的R,其他的先不妨设置为1,然后同样的考虑再增加一个,因为当前选择者肯定不会傻到放到最大堆上(这样的话最大堆就变成必胜态了),那么只能放到其他堆,这样的状况一直会持续到其他n-1个堆都是n-1(不能再多了,因为n是必胜态),这是临界值,所以这个必胜态的R值是上一个必败态的R再加上(n-1)*(n-1)。如此交替即可,那么给定一个x一定能够知道这是必胜堆还是必败堆。最后对于先手者,只要有一堆是必胜的,选择它即可。
M题,比赛的时候搞了半天,最后因为写起来好麻烦就放弃了。看了一下仓鼠的代码是dp的,一下子简单许多。那么就直接放一下他的代码好了(他的叉乘判断顺逆时针方法习惯和我的有点不同,但本质是一样的):

1 #include <bits/stdc++.h> 2 #define x first 3 #define y second 4 using namespace std; 5 typedef pair<int,int> pii; 6 const int N = 1000 + 5; 7 const double inf = 1e18; 8 9 pii p[N]; 10 int st[N], tot; 11 int n; 12 double dp[N]; 13 14 // k->i, k->j 15 bool cross(int i,int j,int k) 16 { 17 pii pa = pii(p[i].x-p[k].x, p[i].y-p[k].y); 18 pii pb = pii(p[j].x-p[k].x, p[j].y-p[k].y); 19 return 1LL*pa.x*pb.y - 1LL*pb.x*pa.y > 0; 20 } 21 double dis(int i,int j) 22 { 23 double xx = p[i].x - p[j].x; 24 double yy = p[i].y - p[j].y; 25 return sqrt(xx*xx + yy*yy); 26 } 27 28 int main() 29 { 30 int T; 31 cin >> T; 32 while(T--) 33 { 34 scanf("%d",&n); 35 for(int i=1;i<=n;i++) scanf("%d%d",&p[i].x,&p[i].y); 36 dp[0] = 0; 37 dp[1] = inf; 38 for(int i=2;i<=n;i++) 39 { 40 dp[i] = inf; 41 tot = 0; 42 43 for(int j=i;j>0;j--) 44 { 45 // 如果储备点大于两个并且存在凹进去的部分,去掉那部分 46 while(tot > 1 && cross(j,st[tot-1],st[tot-2])) tot--; 47 // 如果只有一个储备点,那么是可以直接更新答案的,要注意的是,中间的线段是可以选择去掉的。 48 // 包含第一个点和最后一个点的段是不能被去掉的,所以dp[1]=inf,且dis(i,j)必须存在。 49 if(tot == 1) dp[i] = min(dp[i], min(dp[j-1], dp[j]) + dis(i,j)); 50 st[tot++] = j; 51 } 52 } 53 printf("%.6f ",dp[n]); 54 } 55 return 0; 56 }