考前有二十多分钟准备时间,我中途上了个厕所,再弄弄jsoi,也就十分钟了。代码模板先前没打熟练。题目2:25发下来,我还在打模板,打到2:30才看题目。
我的心态是,“T1,应该能AC吧”,想了一会知道暴力怎么打了,但是半个多小时之后才动手,因为在尝试想出标算。
还看了一眼T2,看看哪个更好拿分。但是被T2吓到了,回来打T1的暴力。
k=0时,很简单,找出一个长度为5的环,点的权值和最大。容易写出暴力。
我先拿了这一部分的分。
当k>0,可以先bfs,建一个新图,然后跟上面一样。
在上面的基础上,我改了改程序。
时间复杂度 O ( n 4 ) O(n^4) O(n4)。
当时分了两次写。其实完全可以一次性把程序打出来,毕竟很简单。
int n, m, k, dis[MAXN];
ll w[MAXN], ans;
bool vh[MAXN], vis[MAXN];
vector<int> g[MAXN], ng[MAXN];
void setIO(string name) {
freopen((name + ".in").c_str(), "r", stdin);
freopen((name + ".out").c_str(), "w", stdout);
}
void build(int beg) {
memset(dis, 0x3f, sizeof(dis)); dis[beg] = 0;
memset(vis, 0, sizeof(vis)); vis[beg] = true;
queue<int> q; q.push(beg);
while (!q.empty()) {
int u = q.front(); q.pop();
if (dis[u] > k) break;
for (int i = 0; i < g[u].size(); ++i) {
int v = g[u][i];
if (!vis[v] && dis[v] > dis[u] + 1) {
dis[v] = dis[u] + 1;
vis[v] = true;
ng[beg].push_back(v);
q.push(v);
}
}
}
}
void dfs_k0(int u, int p, ll s) {
if (p == 4) {
if (vh[u]) ans = max(ans, s);
return;
}
for (int i = 0; i < ng[u].size(); ++i) {
int v = ng[u][i];
if (!vis[v] && v > 1) {
vis[v] = true;
dfs_k0(v, p + 1, s + w[v]);
vis[v] = false;
}
}
}
int main() {
scanf("%d%d%d", &n, &m, &k);
for (int i = 2; i <= n; ++i) {
scanf("%lld", w + i);
}
for (int i = 1; i <= m; ++i) {
int x, y; scanf("%d%d", &x, &y);
g[x].push_back(y);
g[y].push_back(x);
}
for (int i = 1; i <= n; ++i) {
build(i);
}
for (int i = 0; i < ng[1].size(); ++i) {
vh[ng[1][i]] = true;
}
memset(vis, 0, sizeof(vis));
dfs_k0(1, 0, 0);
// if (k == 0) {
// dfs_k0(1, 0, 0);
// }
printf("%lld\n", ans);
fclose(stdin); fclose(stdout);
return 0;
}
数据量2500,是不是 O ( n 2 ) O(n^2) O(n2)或者 O ( n 2 l o g n ) O(n^2logn) O(n2logn)?
想到先枚举和起点相连的两个景点,即景点1、4。有没有什么方法,快速找出中间两个节点,使权值和最大?
这是个变数很多的问题。好难啊。
继续想——约90min就过去了。
游戏中,小L和小Q同时选择一个下标……
读到这句话,我就意识到这题不简单。
更加地,样例2,没有说明。我甚至不知道样例2为什么要输出那些答案!
csp,T2,考博弈论?大纲里不是没有吗?
难道要算选择某一个下标的期望得分吗?可是,小L选择了某一行,小Q选择各列,并不是等可能性的,因为小Q也有智慧。甚至对于不同的行,小Q选择各列的可能性也不同。
小L预判小Q,小Q预判小L……这怎么搞?
一分都拿不到。去看T3,T4。
读了T3,T4,感觉T4更好拿分,先打T4。
如题。
void predfs(int u, int par) {
dep[u] = dep[par] + 1;
fa[u][0] = par;
for (int j = 1; j < MAXP; ++j) {
fa[u][j] = fa[fa[u][j - 1]][j - 1];
}
dis[u] = dis[par] + w[u];
for (int i = 0; i < g[u].size(); ++i) {
int v = g[u][i];
if (v == par) continue;
predfs(v, u);
}
}
int lca(int x, int y) {
if (dep[x] < dep[y]) swap(x, y);
int d = dep[x] - dep[y];
for (int i = 0; i < MAXP; ++i) {
if (d & (1 << i)) x = fa[x][i];
}
if (x == y) return x;
for (int i = MAXP - 1; i >= 0; --i) {
if (fa[x][i] != fa[y][i]) {
x = fa[x][i]; y = fa[y][i];
}
}
return fa[x][0];
}
int main() {
scanf("%d%d%d", &n, &q, &k);
for (int i = 1; i <= n; ++i) {
scanf("%lld", w + i);
}
for (int i = 1; i < n; ++i) {
int a, b; scanf("%d%d", &a, &b);
g[a].push_back(b); g[b].push_back(a);
}
predfs(1, 0);
while (q--) {
int s, t; scanf("%d%d", &s, &t);
if (k == 1) {
int z = lca(s, t);
printf("%lld\n", dis[s] + dis[t] - dis[z] - dis[fa[z][0]]);
}
}
fclose(stdin); fclose(stdout);
return 0;
}
对于k=1,相当于两个点的路径,一个一个走,每个点都得走。
对于k>1,相当于两个点的路径,跳着走。可以把这条路径找到,然后线性dp。
这个算法不具正确性。当时没有想到这种情况:
如图,黄色点并不在s到t的简单路径上,但我们可以经过它。
仍然得了44pts,挺好。
int n, q, k, dep[MAXN], fa[MAXN][MAXP];
ll w[MAXN], dis[MAXN], dp[MAXNN];
vector<int> g[MAXN], ax;
void predfs(int u, int par) {
dep[u] = dep[par] + 1;
fa[u][0] = par;
for (int j = 1; j < MAXP; ++j) {
fa[u][j] = fa[fa[u][j - 1]][j - 1];
}
dis[u] = dis[par] + w[u];
for (int i = 0; i < g[u].size(); ++i) {
int v = g[u][i];
if (v == par) continue;
predfs(v, u);
}
}
int lca(int x, int y) {
if (dep[x] < dep[y]) swap(x, y);
int d = dep[x] - dep[y];
for (int i = 0; i < MAXP; ++i) {
if (d & (1 << i)) x = fa[x][i];
}
if (x == y) return x;
for (int i = MAXP - 1; i >= 0; --i) {
if (fa[x][i] != fa[y][i]) {
x = fa[x][i]; y = fa[y][i];
}
}
return fa[x][0];
}
void set_arr(int x, int y) {
vector<int>().swap(ax);
stack<int> ay;
if (dep[x] < dep[y]) swap(x, y);
while (dep[x] > dep[y]) {
ax.push_back(x);
x = fa[x][0];
}
while (x != y) {
ax.push_back(x); ay.push(y);
x = fa[x][0]; y = fa[y][0];
}
ax.push_back(x);
while (!ay.empty()) {
ax.push_back(ay.top());
ay.pop();
}
}
int main() {
scanf("%d%d%d", &n, &q, &k);
for (int i = 1; i <= n; ++i) {
scanf("%lld", w + i);
}
for (int i = 1; i < n; ++i) {
int a, b; scanf("%d%d", &a, &b);
g[a].push_back(b); g[b].push_back(a);
}
predfs(1, 0);
while (q--) {
int s, t; scanf("%d%d", &s, &t);
if (k == 1) {
int z = lca(s, t);
printf("%lld\n", dis[s] + dis[t] - dis[z] - dis[fa[z][0]]);
}
else {
set_arr(s, t);
memset(dp, 0x3f, sizeof(dp)); dp[0] = w[ax[0]];
for (int i = 1; i < k && i < ax.size(); ++i) {
dp[i] = dp[0] + w[ax[i]];
}
for (int i = k; i < ax.size(); ++i) {
for (int j = 1; j <= k; ++j) {
dp[i] = min(dp[i], dp[i - j] + w[ax[i]]);
}
}
printf("%lld\n", dp[ax.size() - 1]);
}
}
fclose(stdin); fclose(stdout);
return 0;
}
经过二十多分钟的读题分析,只需所有点的出度都是1.
容易写出一个 O ( n 2 ) O(n^2) O(n2)的暴力,用邻接矩阵。
当时打错了几个字母,花了一些时间调试。
#include
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef double db;
const int MOD = 998244353;
const int INF = 0x3f3f3f3f;
const ll LNF = 0x3f3f3f3f3f3f3f3f;
const int MAXN = 1005;
int mod(int x, int y) { return ((x % y) + y) % y; }
int mod(int x) { return ((x % MOD) + MOD) % MOD; }
ll mod(ll x) { return ((x % MOD) + MOD) % MOD; }
void plusmod(int &x) { x = mod(x); }
void plusmod(ll &x) { x = mod(x); }
void setIO(string name) {
freopen((name + ".in").c_str(), "r", stdin);
freopen((name + ".out").c_str(), "w", stdout);
}
int n, m, q, out[MAXN], g[MAXN][MAXN];
bool check() {
for (int i = 1; i <= n; ++i) {
if (out[i] != 1) return false;
}
return true;
}
void work_matrix() {
for (int i = 1; i <= m; ++i) {
int u, v; scanf("%d%d", &u, &v);
g[u][v] = 1; ++out[u];
}
scanf("%d", &q);
while (q--) {
int t, u, v; scanf("%d%d", &t, &u);
if (t & 1) scanf("%d", &v);
if (t == 1) {
g[u][v] = 0; --out[u];
}
else if (t == 2) {
for (int i = 1; i <= n; ++i) {
if (g[i][u] == 1) {
g[i][u] = 0; --out[i];
}
}
}
else if (t == 3) {
g[u][v] = 1; ++out[u];
}
else {
for (int i = 1; i <= n; ++i) {
if (!g[i][u]) {
g[i][u] = 1; ++out[i];
}
}
}
printf(check() ? "YES\n" : "NO\n");
}
}
int main() {
memset(g, -1, sizeof(g));
scanf("%d%d", &n, &m);
work_matrix();
fclose(stdin); fclose(stdout);
return 0;
}
t只能是1或3时,每次 O ( 1 ) O(1) O(1),过程中简单地维护一个玩意,就可以每次 O ( 1 ) O(1) O(1)地检测。
这个部分分太好拿了,对吧!
另外,在拿了前50pts的情况下,后50pts怎么办?我放弃了。实际上,我应当对于后50pts全部输出No,毕竟随便走出一步,总比一步都不走强。
另外,当时我有两个选择:继续拿这仅仅10pts的部分分,或者回去想还有100pts没拿的T2。很显然,我理应选择后者。
int n, m, q, out[MAXN], g[MAXN][MAXN];
bool check() {
for (int i = 1; i <= n; ++i) {
if (out[i] != 1) return false;
}
return true;
}
void work_matrix() {
for (int i = 1; i <= m; ++i) {
int u, v; scanf("%d%d", &u, &v);
g[u][v] = 1; ++out[u];
}
scanf("%d", &q);
while (q--) {
int t, u, v; scanf("%d%d", &t, &u);
if (t & 1) scanf("%d", &v);
if (t == 1) {
g[u][v] = 0; --out[u];
}
else if (t == 2) {
for (int i = 1; i <= n; ++i) {
if (g[i][u] == 1) {
g[i][u] = 0; --out[i];
}
}
}
else if (t == 3) {
g[u][v] = 1; ++out[u];
}
else {
for (int i = 1; i <= n; ++i) {
if (!g[i][u]) {
g[i][u] = 1; ++out[i];
}
}
}
printf(check() ? "YES\n" : "NO\n");
}
}
void work_no24() {
for (int i = 1; i <= m; ++i) {
int u, v; scanf("%d%d", &u, &v);
++out[u];
}
scanf("%d", &q);
while (q--) {
int t, u, v; scanf("%d%d%d", &t, &u, &v);
if (t == 1) --out[u];
else ++out[u];
printf(check() ? "YES\n" : "NO\n");
}
}
int main() {
memset(g, -1, sizeof(g));
scanf("%d%d", &n, &m);
if (n <= 1000) work_matrix();
else work_no24();//结果上来说,是无用的
fclose(stdin); fclose(stdout);
return 0;
}
又回到了这里。此时,还剩20min。
我依旧毫无思路。
剩下的10min,我先把T2全部输出0,然后看了看另外三道题,我有没有文件操作打错,没开long long之类的错误。很快,时间就到了。
T2,大抵是爆0了。
真是一个策略游戏。
待完成。