NOIP2016提高组的难度参差不齐……按照难度从小到大排序讲吧……
Top 6:D1T1 玩具谜题(toy) - 模拟
模拟题。根据小人的朝向和向左/右数来判断走向。注意边界。
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
inline int read() {
int res = 0; bool bo = 0; char c;
while (((c = getchar()) < '0' || c > '9') && c != '-');
if (c == '-') bo = 1; else res = c - 48;
while ((c = getchar()) >= '0' && c <= '9')
res = (res << 3) + (res << 1) + (c - 48);
return bo ? ~res + 1 : res;
}
const int N = 1e5 + 5;
int n, m, v[N]; string s[N];
int main() {
//freopen("toy.in", "r", stdin);
//freopen("toy.out", "w", stdout);
int i, now = 0; n = read(); m = read();
for (i = 1; i <= n; i++) v[i] = read(), cin >> s[i];
for (i = 1; i <= m; i++)
if (v[now + 1] ^ read()) now = (now + read()) % n;
else now = (now - read() + n) % n;
cout << s[now + 1] << endl;
return 0;
}
Top 5:D2T1 组合数问题(problem) - 组合数+前缀和
首先使用公式
Cmn=Cmn−1+Cm−1n−1
预处理
2000
以内的组合数取模
k
的值,判断一下每个值是否为
代码:
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
inline int read() {
int res = 0; bool bo = 0; char c;
while (((c = getchar()) < '0' || c > '9') && c != '-');
if (c == '-') bo = 1; else res = c - 48;
while ((c = getchar()) >= '0' && c <= '9')
res = (res << 3) + (res << 1) + (c - 48);
return bo ? ~res + 1 : res;
}
const int N = 2000;
int m, n, PYZ, c[N + 5][N + 5], col[N + 5][N + 5], cnt[N + 5][N + 5];
int main() {
//freopen("problem.in", "r", stdin);
//freopen("problem.out", "w", stdout);
int i, j, T; T = read(); PYZ = read();
for (i = 0; i <= N; i++) c[i][0] = 1;
for (i = 1; i <= N; i++) for (j = 1; j <= i; j++)
c[i][j] = (c[i - 1][j - 1] + c[i - 1][j]) % PYZ;
for (j = 1; j <= N; j++) for (i = 1; i <= (N - j + 1); i++)
col[j][i] = col[j][i - 1] + (c[i + j - 1][j] == 0);
for (i = 1; i <= N; i++) for (j = 1; j <= i; j++)
cnt[i][j] = cnt[i][j - 1] + col[j][i - j + 1];
while (T--) {
m = read(); n = read();
printf("%d\n", cnt[m][min(m, n)]);
}
return 0;
}
Top 4:D2T3 愤怒的小鸟(angrybirds) - 状压DP
首先知道,三个不在一条直线上的点可以确定一条抛物线。而
(0,0)
是已经固定的点,所以只要枚举两个点,就可以得到一条抛物线。
考虑状态压缩。先预处理出
con[i][j]
,表示第
i
只猪和第
预处理较简单,注意两个猪
y=x1y2−x2y1x1x2(x2−x1)x2+x22y1−x21y2x1x2(x2−x1)x
然后设
f[S]
为当前猪的状态为二进制
S
(为
转移有两种:
1、
f[S]=min(f[S],f[S
&
con[i][j]]+1),i,j∈S,i≠j
2、
f[S]=min(f[S],f[S−2i−1]+1),i∈S
这样就能
O(2nn2)
解决了。
优化:每一个猪是肯定要消灭的,所以可以在第一个转移中,用
S
集合中的第一个数来代替
代码:
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
inline int read() {
int res = 0; bool bo = 0; char c;
while (((c = getchar()) < '0' || c > '9') && c != '-');
if (c == '-') bo = 1; else res = c - 48;
while ((c = getchar()) >= '0' && c <= '9')
res = (res << 3) + (res << 1) + (c - 48);
return bo ? ~res + 1 : res;
}
const int N = 20; const double eps = 1e-9;
int n, f[1 << N], ef[N][N];
double X[N], Y[N];
struct cyx {
double a, b;
cyx() {}
cyx(double _a, double _b) :
a(_a), b(_b) {}
};
cyx quad(double x1, double y1, double x2, double y2) {
if (abs(x1 * x2 * (x2 - x1)) < eps) return cyx(1e20, 1e20);
double a = (x1 * y2 - x2 * y1) / (x1 * x2 * (x2 - x1)),
b = (y1 * x2 * x2 - y2 * x1 * x1) / (x1 * x2 * (x2 - x1));
return a < -eps ? cyx(a, b) : cyx(1e20, 1e20);
}
int Dp(int state) {
if (f[state] != -1) return f[state];
if (!state) return 0;
int i, fir, res = n; for (i = 1; i <= n; i++)
if ((state >> i - 1) & 1)
{fir = i; break;}
for (i = 1; i <= n; i++)
if (((state >> i - 1) & 1)) {
if (i != fir && (state & ef[fir][i]) != state)
res = min(res, Dp(state & ef[fir][i]) + 1);
res = min(res, Dp(state ^ (1 << i - 1)) + 1);
}
return f[state] = res;
}
void work() {
int i, j, k; n = read(); read(); memset(f, -1, sizeof(f));
for (i = 1; i <= n; i++) scanf("%lf%lf", &X[i], &Y[i]);
for (i = 1; i <= n; i++) for (j = i + 1; j <= n; j++)
ef[i][j] = (1 << n) - 1;
for (i = 1; i <= n; i++) for (j = i + 1; j <= n; j++) {
cyx q = quad(X[i], Y[i], X[j], Y[j]);
if (q.a > 1e19) continue;
for (k = 1; k <= n; k++)
if (abs(q.a * X[k] * X[k] + q.b * X[k] - Y[k]) < eps)
ef[i][j] ^= 1 << k - 1;
}
printf("%d\n", Dp((1 << n) - 1));
}
int main() {
//freopen("angrybirds.in", "r", stdin);
//freopen("angrybirds.out", "w", stdout);
int T = read();
while (T--) work();
return 0;
}
Top 3:D2T2 蚯蚓(earthworm) - 队列
容易想到用堆维护蚯蚓长度,对于蚯蚓的增长,就用一个
delta
标记。
但是
O(nlogn)
的复杂度无法承受
n≤7∗106
的数据范围。
当
q=0
的时候,可以很容易得到每次切蚯蚓得到的两段是单调不增的。
但
q≠0
时也满足这个性质。
证明:
如果说这一次切掉的蚯蚓实际长度为
x+delta
,下一次切掉的蚯蚓实际长度为
y+delta+q
,由题意容易得到
x>y
。
那么这一次把蚯蚓切成的两段中,第一段的相对长度为
p(x+delta)−delta−q=p∗x+p∗delta−delta−q
。
下一次把蚯蚓切成的两段中,第一段相对长度为
p(y+delta+q)−delta−2q=p∗y+p∗delta+p∗q−delta−2q
由
0<p<1,q>=0
得
pq−q<0
又由于
x>y
,所以
p(x−y)>0
,即
px−py>pq−q
也就是
px+p∗delta−delta−q>py+p∗delta+p∗q−delta−2q
。第二段同理。即每次切掉的两个蚯蚓长度是单调不增的。
根据这个特性,可以维护
3
个队列,分别为未切的蚯蚓,第一段和第二段。每一次操作就在
代码:
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
inline int read() {
int res = 0; bool bo = 0; char c;
while (((c = getchar()) < '0' || c > '9') && c != '-');
if (c == '-') bo = 1; else res = c - 48;
while ((c = getchar()) >= '0' && c <= '9')
res = (res << 3) + (res << 1) + (c - 48);
return bo ? ~res + 1 : res;
}
const int N = 1e5 + 5, M = 7e6 + 5, INF = 0x3f3f3f3f;
int n, m, q, u, v, t, a[N], Q[4][M + N], H[4], T[4], add;
double p;
int pop() {
int i, id = 0, ans = -INF;
for (i = 1; i <= 3; i++)
if (H[i] < T[i] && Q[i][H[i] + 1] > ans)
id = i, ans = Q[id][H[id] + 1];
return Q[id][++H[id]];
}
int main() {
//freopen("earthworm.in", "r", stdin);
//freopen("earthworm.out", "w", stdout);
int i, cnt = 0; n = read(); m = read(); q = read();
u = read(); v = read(); t = read(); p = (1.0 * u) / (1.0 * v);
for (i = 1; i <= n; i++) a[i] = read();
sort(a + 1, a + n + 1); for (i = n; i >= 1; i--)
Q[1][++T[1]] = a[i];
for (i = 1; i <= m; i++) {
int x = pop() + add, y = (int) (1.0 * x * p), z = x - y;
add += q; y -= add; z -= add;
Q[2][++T[2]] = y; Q[3][++T[3]] = z;
if ((++cnt) == t) printf("%d ", x), cnt = 0;
}
printf("\n"); cnt = 0;
for (i = 1; i <= n + m; i++) {
int x = pop() + add;
if ((++cnt) == t) printf("%d ", x), cnt = 0;
}
printf("\n");
return 0;
}
Top 2:D1T3 换教室(classroom) - Floyd+期望DP
首先使用Floyd求出任意两点之间的最短路,设
i
到
DP模型:设
f[i][j][0/1]
为到了第
i
个时间段,换了
转移有
1、第
i−1
和第
i
个教室都不换。即
2、换第
i−1
个教室,不换第
i
个教室。这时候将答案分成两部分,一部分是
这时候第一部分的期望为
f[i][j][0]=min(f[i][j][0],f[i−1][j][1]+len[di−1][ci]∗ki−1+len[ci−1][ci]∗(1−ki−1)
。
3、不换第
i−1
个教室,换第
i
个教室。这时候也是一样的思想,即
4、第
i−1
个教室和第
i
个教室都换。这时候是情况最多的,有
(1)两个申请都通过。即
len[di−1][di]
,发生的概率为
ki−1∗ki
。
(2)只通过第一个申请。即
len[di−1][ci]
,发生的概率为
ki−1∗(1−ki)
。
(3)只通过第二个申请。即
len[ci−1][di]
,发生的概率为
(1−ki−1)∗ki
。
(4)两个申请都不通过。即
len[ci−1][ci]
,发生的概率为
(1−ki−1)∗(1−ki)
。
得出转移:
f[i][j][1]=min(f[i−1][j−1][1],len[di−1][di]∗ki−1∗ki
+len[di−1][ci]∗ki−1∗(1−ki)+len[ci−1][di]∗(1−ki−1)∗ki
+len[ci−1][ci]∗(1−ki−1)∗(1−ki))
。
代码:
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
inline int read() {
int res = 0; bool bo = 0; char c;
while (((c = getchar()) < '0' || c > '9') && c != '-');
if (c == '-') bo = 1; else res = c - 48;
while ((c = getchar()) >= '0' && c <= '9')
res = (res << 3) + (res << 1) + (c - 48);
return bo ? ~res + 1 : res;
}
void chkmin(double &a, double b) {
a = min(a, b);
}
const int N = 2005, M = 305, INF = 0x3f3f3f3f;
int n, m, v, e, c[N], d[N], ex[M][M];
double K[N], f[N][N][2];
void floyd() {
int i, j, k; for (k = 1; k <= v; k++)
for (i = 1; i <= v; i++) for (j = 1; j <= v; j++)
ex[i][j] = min(ex[i][j], ex[i][k] + ex[k][j]);
for (i = 1; i <= v; i++) ex[i][i] = 0;
}
double solve() {
floyd(); int i, j;
for (i = 1; i <= n; i++) for (j = 0; j <= m; j++)
f[i][j][0] = f[i][j][1] = 1e20;
f[1][0][0] = f[1][1][1] = 0;
for (i = 2; i <= n; i++) for (j = 0; j <= min(i, m); j++) {
chkmin(f[i][j][0], f[i - 1][j][0] +
ex[c[i - 1]][c[i]]);
if (j == 0) continue;
chkmin(f[i][j][0], f[i - 1][j][1] +
1.0 * ex[d[i - 1]][c[i]] * K[i - 1]
+ 1.0 * ex[c[i - 1]][c[i]] * (1.0 - K[i - 1]));
chkmin(f[i][j][1], f[i - 1][j - 1][0] +
1.0 * ex[c[i - 1]][d[i]] * K[i]
+ 1.0 * ex[c[i - 1]][c[i]] * (1.0 - K[i]));
chkmin(f[i][j][1], f[i - 1][j - 1][1] +
1.0 * ex[d[i - 1]][d[i]] * K[i - 1] * K[i] +
1.0 * ex[c[i - 1]][d[i]] * (1.0 - K[i - 1]) * K[i] +
1.0 * ex[d[i - 1]][c[i]] * K[i - 1] * (1.0 - K[i]) +
1.0 * ex[c[i - 1]][c[i]] * (1.0 - K[i - 1]) * (1.0 - K[i]));
}
double res = 1e20;
for (j = 0; j <= min(n, m); j++)
res = min(res, min(f[n][j][0], f[n][j][1]));
return res;
}
int main() {
//freopen("classroom.in", "r", stdin);
//freopen("classroom.out", "w", stdout);
int i, x, y, z; memset(ex, INF, sizeof(ex));
n = read(); m = read(); v = read(); e = read();
for (i = 1; i <= n; i++) c[i] = read();
for (i = 1; i <= n; i++) d[i] = read();
for (i = 1; i <= n; i++) scanf("%lf", &K[i]);
while (e--) {
x = read(); y = read(); z = read();
if (x != y) ex[x][y] = ex[y][x] = min(ex[x][y], z);
}
printf("%.2lf\n", solve());
return 0;
}
Top 1:D1T2 天天爱跑步(running) - dfs+统计
这题是2016中最难的。首先,还是把原图视为有根树,求出每个
Si
和
Ti
的lca即
lcai
。
可以发现,第
u
个观察员能观察到的玩家数目为:
在
怎么求前两个内容呢?
先考虑第一个,设
第二个内容也是差不多的思想。对于一个玩家
考虑怎样实现。对于第一个内容,容易想到用一个计数器
第二个内容的实现也是差不多,也是用dfs和增量维护子树内的信息,用vector或邻接表进行去重。但是注意细节:
dep[u]−Wu
有可能是负数,因此统计数组的下标要加上一个定值。
复杂度:如果用倍增求lca,那么复杂度为
O(nlogn)
,如果用Tarjan离线求lca,那么复杂度为
O(n)
。
代码:
#include <cmath>
#include <cstdio>
#include <vector>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
inline int read() {
int res = 0; bool bo = 0; char c;
while (((c = getchar()) < '0' || c > '9') && c != '-');
if (c == '-') bo = 1; else res = c - 48;
while ((c = getchar()) >= '0' && c <= '9')
res = (res << 3) + (res << 1) + (c - 48);
return bo ? ~res + 1 : res;
}
const int N = 3e5 + 5, LogN = 22, M = 3e5;
int n, m, ecnt, nxt[N << 1], adj[N], go[N << 1], tim[N], S[N], T[N],
L[N], dep[N], fa[N][LogN], up[N], down[N], res[N], cnt[N << 1],
cnt_s[N], cnt_t[N], len[N], md;
vector<int> lca_s[N], lca_t[N], s_t[N];
void add_edge(int u, int v) {
nxt[++ecnt] = adj[u]; adj[u] = ecnt; go[ecnt] = v;
}
void dfs(int u, int fu) {
dep[u] = dep[fu] + 1; fa[u][0] = fu;
md = max(md, dep[u]);
int i; for (i = 1; i <= 20; i++)
fa[u][i] = fa[fa[u][i - 1]][i - 1];
for (int e = adj[u], v; e; e = nxt[e])
if ((v = go[e]) != fu) dfs(v, u);
}
int lca(int u, int v) {
if (dep[u] < dep[v]) swap(u, v);
int i; for (i = 20; i >= 0; i--) {
if (dep[fa[u][i]] >= dep[v]) u = fa[u][i];
if (u == v) return u;
}
for (i = 20; i >= 0; i--)
if (fa[u][i] != fa[v][i]) {
u = fa[u][i];
v = fa[v][i];
}
return fa[u][0];
}
void dfs1(int u, int fu) {
int i, cc = lca_s[u].size(), tot = cnt[up[u]];
for (int e = adj[u], v; e; e = nxt[e])
if ((v = go[e]) != fu)
dfs1(v, u);
cnt[dep[u]] += cnt_s[u];
if (up[u] <= md) res[u] += cnt[up[u]] - tot;
for (i = 0; i < cc; i++) cnt[dep[lca_s[u][i]]]--;
}
void dfs2(int u, int fu) {
int i, cc = lca_t[u].size(), ff = s_t[u].size(), tot = cnt[down[u] + M];
for (int e = adj[u], v; e; e = nxt[e])
if ((v = go[e]) != fu)
dfs2(v, u);
for (i = 0; i < ff; i++) cnt[s_t[u][i] + M]++;
res[u] += cnt[down[u] + M] - tot;
for (i = 0; i < cc; i++) cnt[lca_t[u][i] + M]--;
}
int main() {
//freopen("running.in", "r", stdin);
//freopen("running.out", "w", stdout);
int i, x, y; n = read(); m = read();
for (i = 1; i < n; i++) {
x = read(); y = read();
add_edge(x, y); add_edge(y, x);
}
dfs(1, 0); for (i = 1; i <= n; i++)
tim[i] = read(), up[i] = dep[i] + tim[i], down[i] = dep[i] - tim[i];
for (i = 1; i <= m; i++) S[i] = read(), T[i] = read(),
L[i] = lca(S[i], T[i]), cnt_s[S[i]]++, cnt_t[T[i]]++,
len[i] = dep[S[i]] + dep[T[i]] - (dep[L[i]] << 1),
lca_s[L[i]].push_back(S[i]), lca_t[L[i]].push_back(dep[T[i]] - len[i]),
s_t[T[i]].push_back(dep[T[i]] - len[i]);
dfs1(1, 0); memset(cnt, 0, sizeof(cnt)); dfs2(1, 0);
for (i = 1; i <= m; i++) if (dep[S[i]] - dep[L[i]] == tim[L[i]])
res[L[i]]--;
for (i = 1; i <= n; i++) printf("%d ", res[i]); printf("\n");
return 0;
}
总结
蒟蒻写完这份题解的之后的第7天,NOIP2017也要开始了。祝各位神犇大佬AK NOIP,AK 省选,AK NOI,AK CTSC,AK IOI,AK ACM系列各大比赛!