Egyptian Collegiate Programming Contest (ECPC 2015)

  题目链接: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 }
M题
原文地址:https://www.cnblogs.com/zzyDS/p/6622191.html