Noi2010部分题解

心血来潮想从2010年的Noi题目开始刷起。

虽然我很菜但是还是会坚持把能写的题目都写完的。


超级钢琴:

一句话题意:从一个序列中选$k$段互不相同的区间(区间长度在$l$和$r$之间),求这$k$个区间的和的最大值。

先求个前缀和,然后枚举左端点,对于每个左端点$o$,要找到最大的区间和就相当于从$[o+l-1,o+r-1]$之间选一个最大值。

于是定义四元组$(o,l,r,t)$表示以$o$为左端点,右端点的选择区间为$[l,r]$,区间和最大时右端点为$t$的情况。

我们把之前枚举左端点得到的答案都丢进一个堆里,然后每次取出一个四元组后,$ans$累加它的贡献,然后把$(o,l,t-1)$和$(o,t+1,r)$再丢进堆里。

求最大值用$ST$表即可,时间复杂度为:$O(klogn)$。

代码:

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 
 4 #define int long long
 5 const int MAXN = 1000010;
 6 
 7 int n, k, L, R, ans, sum[MAXN];
 8 int f[MAXN][21], Log2[MAXN];
 9 
10 void init()
11 {
12     Log2[1] = 0; for(int i = 2; i <= n; ++i) Log2[i] = Log2[i >> 1] + 1;
13     for(int i = 1; i <= n; ++i) f[i][0] = i;
14     for(int j = 1; j < 21; ++j)
15         for(int i = 1; i <= n; ++i)
16             f[i][j] = sum[f[i][j - 1]] > sum[f[i + (1 << (j - 1))][j - 1]] ? f[i][j - 1] : f[i + (1 << (j - 1))][j - 1];
17 }
18 
19 int query(int l, int r)
20 {
21     int x = Log2[r - l + 1];
22     return sum[f[l][x]] > sum[f[r - (1 << x) + 1][x]] ? f[l][x] : f[r - (1 << x) + 1][x];
23 }
24 
25 struct Node
26 {
27     int o, l, r, p;
28     bool operator < (const Node &x) const
29     {
30         return sum[p] - sum[o - 1] < sum[x.p] - sum[x.o - 1];
31     }
32 };
33 priority_queue<Node> q;
34 
35 signed main()
36 {
37     scanf("%lld %lld %lld %lld", &n, &k, &L, &R);
38     for(int i = 1; i <= n; ++i) scanf("%lld", &sum[i]), sum[i] += sum[i - 1];
39     init();
40     for(int i = 1; i <= n; ++i)
41         if(i + L - 1 <= n)
42             q.push((Node){i, i + L - 1, min(i + R - 1, n), query(i + L - 1, min(i + R - 1, n))});
43     while(k--)
44     {
45         Node x = q.top(); q.pop();
46         ans += sum[x.p] - sum[x.o - 1];
47         if(x.l != x.p) q.push((Node){x.o, x.l, x.p - 1, query(x.l, x.p - 1)});
48         if(x.r != x.p) q.push((Node){x.o, x.p + 1, x.r, query(x.p + 1, x.r)});
49     }
50     printf("%lld\n", ans);
51     return 0;
52 }

海拔:

上坡要消耗体力,而且下坡不能增加体力,那么容易发现,每个点的海拔只可能为0或1。

再进一步的考虑,如果一个0的联通块的四周都是1,而且这个联通块不包含左上角的0,那么显然这些0都变成1会更优,一个不包含右下角的1的联通块若四周都是0,则变成0会更优。

基于上面的结论,最终的答案一定是所有的0构成了一个联通块,所有的1构成了一个联通块,且只有连接$0,1$两个点之间的边对答案有贡献。

于是问题就转换为了最小割的问题,以左上角为源点,右下角为汇点,边权为流量,跑一遍最大流即可。

当$n = 500$时,$dinic$显然会超时。注意到这是一个网格图,所以我们将其转换为对偶图求最短路即可。

注意到原图中的边是有向的,我们不妨假定对偶图边经过的方向,左边海拔为0,右边海拔为1,然后只要算0到1的,所以就是正方向的权值。所以我们只需要把方向相反的两条边在对偶图中也构出方向相反的即可。

代码:

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 
 4 const int MAXN = 300010;
 5 const int MAXM = 1500010;
 6 
 7 int n, s, t, dis[MAXN];
 8 int tot, Head[MAXN], Next[MAXM], ver[MAXM], edge[MAXM];
 9 
10 struct Node
11 {
12     int u, dis;
13     bool operator < (const Node &x) const
14     {
15         return dis > x.dis;
16     }
17 };
18 priority_queue<Node> q;
19 
20 void add(int u, int v, int z)
21 {
22     ver[++tot] = v;
23     edge[tot] = z;
24     Next[tot] = Head[u];
25     Head[u] = tot;
26 }
27 
28 void dijkstra()
29 {
30     memset(dis, 0x3f, sizeof(dis));
31     q.push((Node){s, 0}); dis[s] = 0;
32     while(!q.empty())
33     {
34         Node x = q.top(); q.pop();
35         if(dis[x.u] != x.dis) continue;
36         for(int i = Head[x.u]; i; i = Next[i])
37         {
38             int y = ver[i], z = edge[i];
39             if(dis[y] > dis[x.u] + z)
40             {
41                 dis[y] = dis[x.u] + z;
42                 q.push((Node){y, dis[y]});
43             }
44         }
45     }
46 }
47 
48 int main()
49 {
50     scanf("%d", &n); int x;
51     s = 0, t = n * n + 1;
52     for(int i = 1; i <= n + 1; ++i)
53         for(int j = 1; j <= n; ++j)
54         {
55             scanf("%d", &x);
56             if(i == 1) add(j, t, x);
57             else if(i == n + 1) add(s, (n - 1) * n + j, x);
58             else add((i - 1) * n + j, (i - 2) * n + j, x);
59         }
60     for(int i = 1; i <= n; ++i)
61         for(int j = 0; j <= n; ++j)
62         {
63             scanf("%d", &x);
64             if(j == 0) add(s, (i - 1) * n + 1, x);
65             else if(j == n) add(i * n, t, x);
66             else add((i - 1) * n + j, (i - 1) * n + j + 1, x);
67         }
68     for(int i = 1; i <= n + 1; ++i)
69         for(int j = 1; j <= n; ++j)
70         {
71             scanf("%d", &x);
72             if(i == 1) add(t, j, x);
73             else if(i == n + 1) add((n - 1) * n + j, s, x);
74             else add((i - 2) * n + j, (i - 1) * n + j, x);
75         }
76     for(int i = 1; i <= n; ++i)
77         for(int j = 0; j <= n; ++j)
78         {
79             scanf("%d", &x);
80             if(j == 0) add((i - 1) * n + 1, s, x);
81             else if(j == n) add(t, i * n, x);
82             else add((i - 1) * n + j + 1, (i - 1) * n + j, x);
83         }
84     dijkstra();
85     printf("%d\n", dis[t]);
86     return 0;
87 }

航空管制

第一问:

首先考虑第二类限制,对于每类限制$(a,b)$,我们连一条边$(a,b)$,这样题目就转化为了求原图的一个满足第一类限制的拓扑序。

对于第一类限制,我们有一个贪心的想法就是让$k_i$小的点尽早出现,容易发现这样是最优的。

于是我们有一个想法,就是根据$k_i$的值建一个小根堆进行拓扑排序。

很可惜这样是错的,因为我们无法根据一条路径的头部节点大小来决定顺序,但是可以根据一条路径的尾部节点大小来决定顺序。

具体原因可以看HDU 4857这道题的题解

于是我们建反图进行拓扑排序,根据$n-k_i$来建小根堆即可。

第二问:

我们要求出每个点在反图中最晚的起飞时间,于是我们在拓扑排序的过程中做一点改动:

当我们考虑一个点的时候,首先不管它(即使它没有度了也不放进去),然后当什么时候我们发现其它的点已经不满足情况了,那么就是这个点必须要出现的位置,也就是正图中的最早位置。

总的时间复杂度为:$O(n^2logn)$

PS:这题卡常,还要手写堆。。。

双倍经验福利:[HNOI2015]菜肴制作

代码:

  1 #include<bits/stdc++.h>
  2 using namespace std;
  3 
  4 inline char Getchar()
  5 {
  6     static char buf[1000010], *p1 = buf, *p2 = buf;
  7     return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1000000, stdin), p1 == p2) ? EOF : *p1++;
  8 }
  9 
 10 inline int Read()
 11 {
 12     int x = 0, f = 1; char s = Getchar();
 13     for(; !isdigit(s); s = Getchar()) if(s == '-') f = -1;
 14     for(; isdigit(s); s = Getchar()) x = x * 10 + s - 48;
 15     return x * f;
 16 }
 17 
 18 inline void Print(int x)
 19 {
 20     if(x > 9) Print(x / 10);
 21     putchar(x % 10 + 48);
 22 }
 23 
 24 const int MAXN = 10010;
 25 
 26 int n, m, cnt, ans[MAXN];
 27 int k[MAXN], du[MAXN], in[MAXN];
 28 int tot, Head[MAXN], ver[MAXN], Next[MAXN];
 29 
 30 struct Node
 31 {
 32     int val, id;
 33     inline bool operator < (const Node &x) const
 34     {
 35         return val > x.val || (val == x.val && id > x.id);
 36     }
 37     inline bool operator > (const Node &x) const
 38     {
 39         return val < x.val || (val == x.val && id < x.id);
 40     }
 41 };
 42 
 43 struct Heap
 44 {
 45     Node heap[MAXN]; int siz;
 46     
 47     inline void up(int p)
 48     {
 49         while(p > 1)
 50         {
 51             if(heap[p] > heap[p >> 1])
 52             {
 53                 swap(heap[p], heap[p >> 1]);
 54                 p >>= 1;
 55             }
 56             else break;
 57         }
 58     }
 59     
 60     inline void down(int p)
 61     {
 62         int s = p << 1;
 63         while(s <= siz)
 64         {
 65             if(s < siz && heap[s] < heap[s + 1]) ++s;
 66             if(heap[s] > heap[p])
 67             {
 68                 swap(heap[s], heap[p]);
 69                 p = s, s = p << 1;
 70             }
 71             else break;
 72         }
 73     }
 74     
 75     inline void Insert(Node val)
 76     {
 77         heap[++siz] = val;
 78         up(siz);
 79     }
 80     
 81     inline Node Gettop()
 82     {
 83         return heap[1];
 84     }
 85     
 86     inline void remove()
 87     {
 88         heap[1] = heap[siz--];
 89         down(1);
 90     }
 91 }H;
 92 
 93 inline void add(int u, int v)
 94 {
 95     ver[++tot] = v;
 96     Next[tot] = Head[u];
 97     Head[u] = tot;
 98 }
 99 
100 inline int solve(int now)
101 {
102     cnt = H.siz = 0;
103     for(register int i = 1; i <= n; ++i) du[i] = in[i];
104     for(register int i = 1; i <= n; ++i) if(!du[i]) H.Insert((Node){n - k[i], i});
105     while(H.siz)
106     {
107         Node x = H.Gettop(); H.remove();
108         if(x.id == now) continue;
109         if(n - cnt > k[x.id]) return n - cnt;
110         ++cnt;
111         for(register int i = Head[x.id]; i; i = Next[i])
112         {
113             int y = ver[i];
114             if(--du[y] == 0) H.Insert((Node){n - k[y], y});
115         }
116     }
117     return n - cnt;
118 }
119 
120 int main()
121 {
122     n = Read(), m = Read();
123     for(register int i = 1; i <= n; ++i) k[i] = Read();
124     for(register int i = 1; i <= m; ++i)
125     {
126         int u = Read(), v = Read();
127         add(v, u);
128         ++in[u];
129     }
130     for(register int i = 1; i <= n; ++i) du[i] = in[i];
131     for(register int i = 1; i <= n; ++i) if(!du[i]) H.Insert((Node){n - k[i], i});
132     while(H.siz)
133     {
134         Node x = H.Gettop(); H.remove();
135         ans[++cnt] = x.id;
136         for(register int i = Head[x.id]; i; i = Next[i])
137         {
138             int y = ver[i];
139             if(--du[y] == 0) H.Insert((Node){n - k[y], y});
140         }
141     }
142     for(register int i = cnt; i; --i) Print(ans[i]), putchar(' ');
143     putchar('\n');
144     for(register int i = 1; i <= n; ++i) Print(solve(i)), putchar(' ');
145     putchar('\n');
146     return 0;
147 }

能量采集

根据数论知识我们可以知道:从原点到点$(i,j)$的线段上一共有$gcd(i, j) - 1$个点(不包含原点和终点)。所以题目所求即为:

$$\sum_{i = 1}^{n} \sum_{j = 1}^{m} [2 * (gcd(i, j) - 1) + 1]$$

 即:

$$2 * [\sum_{i = 1}^{n} \sum_{j = 1}^{m} gcd(i, j)] - n * m$$

来一发莫比乌斯反演可得:

$$\sum_{i = 1}^{n} \sum_{j = 1}^{m} gcd(i, j) = \sum_{i = 1}^{max(n, m)} \sum_{d = 1}^{min(\frac{n}{i}, \frac{m}{i})} \mu(d) * \frac{n}{di} * \frac{m}{di}$$

时间复杂度为:$O(n\sqrt{n})$

代码:

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 
 4 #define int long long
 5 const int MAXN = 100010;
 6 
 7 int mu[MAXN], cnt, prime[MAXN];
 8 bool vis[MAXN];
 9 
10 void get_mu(int n)
11 {
12     mu[1] = 1;
13     for(int i = 2; i <= n; ++i)
14     {
15         if(!vis[i]) {prime[++cnt] = i; mu[i] = -1;}
16         for(int j = 1; j <= cnt && i * prime[j] <= n; ++j)
17         {
18             vis[i * prime[j]] = 1;
19             if(i % prime[j] == 0) break;
20             mu[i * prime[j]] = -mu[i];
21         }
22     }
23     for(int i = 1; i <= n; ++i) mu[i] += mu[i - 1];
24 }
25 
26 int calc(int n, int m, int k)
27 {
28     int ans = 0; if(n > m) swap(n, m);
29     for(int l = 1, r; l <= n / k; l = r + 1)
30     {
31         r = min((n / k) / (n / k / l), (m / k) / (m / k / l));
32         ans += (mu[r] - mu[l - 1]) * (n / k / l) * (m / k / l);
33     }
34     return ans;
35 }
36 
37 signed main()
38 {
39     get_mu(MAXN - 1); int n, m, ans = 0;
40     scanf("%lld %lld", &n, &m); if(n < m) swap(n, m);
41     for(int k = 1; k <= n; ++k) ans += k * calc(n, m, k);
42     ans = 2ll * ans - (n * m);
43     printf("%lld\n", ans);
44     return 0;
45 }

猜你喜欢

转载自www.cnblogs.com/Aegir/p/10732604.html
今日推荐