D1T1 小凯的疑惑
给两个互素的数aa,bb。求满足条件最大的整数nn使得aa,bb无法通过非负系数线性组合出nn。即
\nexists x, y \in \mathbb N \text{ sat. }
ax + by = n
数据范围a, b \leq 10^9a,b≤109
算法1
考虑dp。
dp[x] = dp[x - a] \text{ or } dp[x - b]dp[x]=dp[x−a] or dp[x−b]
初始边界dp[0] = \text{true}dp[0]=true。如果连续的一段长度a都有dp值为true,则说明已经不可能再有拼不到的了。
时间复杂度:\Theta(ab)Θ(ab)
得人60\%60%。
算法2
从n=abn=ab向下遍历。通过扩展欧几里得得到ax + by = 1ax+by=1的解。则有-ax - by = -1−ax−by=−1。一点点向下搜索。
时间复杂度:\Theta(a + b)Θ(a+b)
将来会给出解释。得分60\%60%。
算法2
不妨用算法1的暴力来测些数据找规律。
输入:
10007 10009
输出:
100140047
这个数好像还蛮整的呢!最后验证发现是ab - a - bab−a−b。
与算法1对拍,发现完全ok!
正确性证明:不会证orz
时间复杂度:\Theta(1)Θ(1),预计得分100\%100%。
样例代码
#include <cstdio>
using namespace std;
int main() {
long long a, b;
scanf("%lld%lld", &a, &b);
printf("%lld\n", a * b - a - b);
return 0;
}
D1T2 时间复杂度
有一种只有循环语句的语言:
F i x y
...
E
i是命名的变量,x和y只可能是1~100的整数或极大参量nn。询问整个程序运行的时间复杂度与输入给定的是否一致。如果循环嵌套内变量命名重复或者循环不匹配,输出编译错误提示ERR
。
<del>算法1</del>
这道题有个p的算法?
其实就是糟心输入+拿栈暴力模拟+各种糟心分类讨论。
样例代码
#include <climits>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <functional>
#include <stack>
#define LOG(FMT...) // if (t == 6) fprintf(stderr, FMT)
using namespace std;
char s[100];
bool used[256];
int main() {
int t, l, inc, c1, c2, c3, p;
char cur;
bool flag;
scanf("%d", &t);
while (t--) {
scanf("%d%s", &l, s);
if (s[2] == '1')
inc = 0;
else
sscanf(s + 4, "%d", &inc);
flag = false;
gets(s);
stack<int> cp, cp2;
stack<char> prm;
cp2.push(0);
memset(used, 0, sizeof(used));
while (l--) {
// LOG("READ\n");
gets(s);
if (flag)
continue;
if (s[0] == 'E') {
if (prm.empty()) {
flag = true;
continue;
}
c1 = cp.top();
cp.pop();
c3 = cp2.top();
cp2.pop();
c2 = cp2.top();
cp2.pop();
LOG("E: %d %d %d\n", c1, c2, c3);
if (c3 == -1)
c3 = 0;
if (c1 != -1)
c1 += c3;
if (c1 == -1)
cp2.push(c2); // ei died at this line
else
cp2.push(max(c1, c2));
used[prm.top()] = false;
prm.pop();
continue;
}
if (used[s[2]]) {
flag = true;
continue;
}
used[s[2]] = true;
prm.push(s[2]);
p = 4;
if (s[p] == 'n') {
c1 = 1000;
p += 2;
} else {
sscanf(s + p, "%d", &c1);
if (c1 >= 100)
++p;
if (c1 >= 10)
++p;
++p;
}
while (s[p] == ' ')
++p;
if (s[p] == 'n')
c2 = 1000;
else
sscanf(s + p, "%d", &c2);
cp2.push(-1);
LOG("F: %d %d\n", c1, c2);
if (c1 > c2)
cp.push(-1);
else if (c2 == 1000 && c1 != 1000)
cp.push(1);
else
cp.push(0);
}
c1 = cp2.top();
if (c1 == -1)
c1 = 0;
if (!prm.empty())
flag = true;
if (flag)
puts("ERR");
else
puts(c1 == inc ? "Yes" : "No");
LOG("%d %d\n", cp2.top(), inc);
}
return 0;
}
D1T3 逛公园
给一个非负权有向图,求其中从1到nn的路径(可以不是简单路径)中长度与最短路差距不超过kk的。如果有无数条,特别地输出-1。
数据范围n \leq 10^5, m \leq 2 \times 10^5, k \leq 50n≤105,m≤2×105,k≤50
算法1
回顾一下最短路计数问题。
根据三角不等式,正权回路上,两点之间的最短路径必然无环,所以从原点ss走出的最短路径构成一个SPDAG(Shortest Path DAG)。
在这种情况下,可以先跑一遍最短路,处理出ss到每个点的最短距离dis[u]dis[u]。可写出对边是否在最短路上的判定
dis[v] = dis[u] + w(u, v)dis[v]=dis[u]+w(u,v)
故可列关于当前节点的状态转移方程
```cpp dp[u] = \left\{ \begin{array}{rl} 1 & \text{if} & u = t \\ \sum_{(u,v)\in G, dis[v] = dis[u] + w(u, v)} dp[v] & \text{else} \end{array} ``` \right.‘‘‘cppdp[u]={1∑(u,v)∈G,dis[v]=dis[u]+w(u,v)dp[v]ifelseu=t‘‘‘
可过无0权且k=0k=0的点。
时间复杂度\Theta(m\log m) + \Theta(m) = \Theta(m \log m)Θ(mlogm)+Θ(m)=Θ(mlogm)
预计得分15\%15%。
算法2
考虑无0权的情况。
因为三角不等式依旧成立,可知不可能存在无穷条符合条件的路径。
考虑对原图分层,令dp[u][j]dp[u][j]表示从ss到uu比最短路径已经多走了jj个单位长度的情况下,满足条件的路径数。可列状态转移方程
```cpp dp[u][j] = \left\{ \begin{array}{rl} 1 & \text{if} & u = t \\ 0 & \text{else} \end{array} ``` \right. + \sum\_{(u,v) \in G} dp[v][dis[u] + j + w(u, v) - dis[v]]‘‘‘cppdp[u][j]={10ifelseu=t‘‘‘+∑_(u,v)∈Gdp[v][dis[u]+j+w(u,v)−dis[v]]
不过对于j > kj>k,置dp[u][j] = 0dp[u][j]=0。
注意到走到终点后可以再绕圈子,所以终点不是直接置0。
最后的ans取dp[s][0]dp[s][0]即可。
接下来讨论该动态规划的复杂度,状态数为nknk个,但对于相同的jj,工作量总和其实是边数之和,则有总工作量O(mk)O(mk)
时间复杂度\Theta(m \log m) + O(mk) = O(m(k + \log m))Θ(mlogm)+O(mk)=O(m(k+logm))
预计得分85\%85%。
算法3
0权并不可怕,因为三角不等式的性质并没有被完全破坏。
我们发现,如果出现无穷条路径,是因为存在一个符合要求的路径上相邻了一个0权环。这时,就会在dfs过程中发生循环调用。考虑同时用visvis数组做拓扑检测,如果拓扑排序失败,则说明有无穷条符合要求的路径。特别的,我们需要对无法到达终点的状态进行剪枝,因为此时发现的0环无意义。即我们在反向图跑一遍tt的最短路处理出dis2[]dis2[]。做剪枝
dis[u]+dis2[u]+j - dis[t] > k \Rightarrow dp[u][j] = 0dis[u]+dis2[u]+j−dis[t]>k⇒dp[u][j]=0
时间复杂度同算法2。
预计得分100\%100%。
样例代码
#include <climits>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <algorithm>
#include <functional>
#include <queue>
#define LOG(FMT...) // fprintf(stderr, FMT)
using namespace std;
struct node {
int u, step;
node() {}
node(int u, int step) : u(u), step(step) {}
bool operator>(const node& x) const
{ return step > x.step; }
};
struct edge {
int v, w;
edge* next;
};
const int N = 100010;
int n, m, k, p, sp;
edge *pp;
int dis1[N], dis2[N];
edge *g1[N], *g2[N];
edge pool[N << 2];
int dp[N][51];
bool vis[N][51];
void add_edge(edge** gp, int u, int v, int w);
void dij(int* disp, edge** gp, int u);
bool dfs(int u, int ck);
int main() {
int t, u, v, w;
scanf("%d", &t);
while (t--) {
pp = pool;
memset(dis1, -1, sizeof(dis1));
memset(dis2, -1, sizeof(dis2));
memset(g1, 0, sizeof(g1));
memset(g2, 0, sizeof(g2));
memset(dp, -1, sizeof(dp));
memset(vis, 0, sizeof(vis));
scanf("%d%d%d%d", &n, &m, &k, &p);
while (m--) {
scanf("%d%d%d", &u, &v, &w);
add_edge(g1, u, v, w);
add_edge(g2, v, u, w);
}
dij(dis1, g1, 1);
dij(dis2, g2, n);
sp = dis2[1];
if (dfs(1, 0))
puts("-1");
else
printf("%d\n", dp[1][0]);
}
return 0;
}
inline void add_edge(edge** gp, int u, int v, int w) {
pp->v = v;
pp->w = w;
pp->next = gp[u];
gp[u] = pp;
++pp;
}
inline void dij(int* disp, edge** gp, int u) {
priority_queue<node, vector<node>, greater<node> > q;
node t;
disp[u] = 0;
q.push(node(u, 0));
while (!q.empty()) {
t = q.top();
q.pop();
u = t.u;
if (disp[u] < t.step)
continue;
for (edge* p = gp[u]; p; p = p->next)
if (disp[p->v] == -1 || disp[p->v] > disp[u] + p->w) {
disp[p->v] = disp[u] + p->w;
q.push(node(p->v, disp[p->v]));
}
}
}
bool dfs(int u, int ck) {
if (vis[u][ck])
return true;
if (~dp[u][ck])
return false;
vis[u][ck] = true;
if (u == n)
dp[u][ck] = 1;
else
dp[u][ck] = 0;
int cp;
for (edge* p = g1[u]; p; p = p->next) {
cp = dis1[u] + p->w + ck - dis1[p->v];
if (cp <= k && dis1[u] + ck + p->w + dis2[p->v] - sp <= k) {
if (dfs(p->v, cp))
return true;
dp[u][ck] += dp[p->v][cp];
if (dp[u][ck] >= ::p)
dp[u][ck] -= ::p;
}
}
vis[u][ck] = false;
return false;
}
D2T1 奶酪
有一个3维空间下的无限广阔的奶酪。可以表示成在0 \le z \le h0≤z≤h的区域都有奶酪。但是存在例外即有一些球形空腔可表示为\sqrt{(x - x_i)^2 + (y - y_i) ^ 2 + (z - z_i)^2} \le r(x−xi)2+(y−yi)2+(z−zi)2
有多组输入t \le 20t≤20,奶酪洞数量n \le 10^3n≤103,其他数值x, y, z, r, h \le 10^9x,y,z,r,h≤109
算法1
枚举两个球能不能走到,并查集合并。
其实不用精度损失,直接将双方平方,能通过当且仅当
(x\_i - x\_j)^2 + (y\_i - y\_j) ^ 2 + (z\_i - z\_j)^2 \le (2r)^2(x_i−x_j)2+(y_i−y_j)2+(z_i−z_j)2≤(2r)2
单个输入有时间复杂度\Theta(n^2) + \Theta(n \alpha(n)) = \Theta(n^2)Θ(n2)+Θ(nα(n))=Θ(n2)。
预计得分100\%100%
样例代码
#include <climits>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <algorithm>
#include <functional>
#define LOG(FMT...) fprintf(stderr, FMT)
using namespace std;
typedef long long ll;
const int N = 1010;
int n, S, T;
ll h, r, d2;
int f[N], rk[N];
ll x[N], y[N], z[N];
ll square(ll x);
int find(int x);
void merge(int x, int y);
int main() {
int t;
scanf("%d", &t);
while (t--) {
scanf("%d%lld%lld", &n, &h, &r);
for (int i = 1; i <= n; ++i)
scanf("%lld%lld%lld", &x[i], &y[i], &z[i]);
d2 = square(r * 2);
S = 0;
T = n + 1;
for (int i = n + 1; ~i; --i)
f[i] = i;
memset(rk, 0, sizeof(rk));
for (int i = 1; i <= n; ++i) {
if (z[i] <= r && z[i] >= -r)
merge(S, i);
if (z[i] >= h - r && z[i] <= h + r)
merge(T, i);
}
for (int i = 1; i <= n; ++i)
for (int j = 1; j < i; ++j)
if (square(x[i] - x[j]) + square(y[i] - y[j]) + square(z[i] - z[j]) <= d2)
merge(i, j);
puts(find(S) == find(T) ? "Yes" : "No");
}
return 0;
}
inline ll square(ll x)
{ return x * x; }
int find(int x) {
if (f[x] == x)
return x;
return f[x] = find(f[x]);
}
inline void merge(int x, int y) {
x = find(x);
y = find(y);
if (x == y)
return;
if (rk[x] > rk[y]) {
f[y] = x;
return;
}
if (rk[x] < rk[y]) {
f[x] = y;
return;
}
f[y] = x;
++rk[x];
}
D2T2 宝藏
算法1
对于部分的数据点是一颗树。只用枚举从哪个点开始开路即可。
时间复杂度\Theta(n^2)Θ(n2),预计得分30\%30%。
算法2
考虑进行dp。
dp[S][u][j]dp[S][u][j]表示现在要规划的点集合是SS,其起点在ii,起点的里出发点已经经过了jj个节点。
易列方程
dp[S][u][j] = \min\_{A \subset S} \min\_{v \in A, (u, v) \in G} ( dp[A][v][j + 1] + dp[\complement\_S A][u][j] + w(u, v) \times j )dp[S][u][j]=min_A⊂Smin_v∈A,(u,v)∈G(dp[A][v][j+1]+dp[∁_SA][u][j]+w(u,v)×j)
有边界条件dp[\{u\}][u][j] = 0dp[{u}][u][j]=0。
时间复杂度\Theta(3^n n^3)Θ(3nn3)。预计得分80\%80%。
算法3
会有许多点到起点经过的点数是相同的。考虑优化这些部分?
dp[S][j]dp[S][j]表示当前SS内的点如果向外联,则代价是w \times jw×j。此时规划出的最优方案。
dp[S][j] = \min\_{A \subset S} (dp[A][j + 1] + j \times cost[\complement\_S A][\complement\_V S])dp[S][j]=min_A⊂S(dp[A][j+1]+j×cost[∁_SA][∁_VS])
这里要预处理cost[A][S]cost[A][S]表示AA中的每一点uu到SS的某一个点中距离的最小值
时间复杂度\Theta(3^n n) + \Theta(3^n n) = \Theta(3^n n)Θ(3nn)+Θ(3nn)=Θ(3nn),预计得分100\%100%。
样例代码
我这里写的cost是点与集合的,复杂度是\Theta(3^n n^2)Θ(3nn2)。其实按数据范围也是可过的。
#include <cstdio>
#include <cstring>
#include <algorithm>
#define LOG(FMT...) // fprintf(stderr, FMT)
using namespace std;
const int N = 12, L = 1 << N;
int n, U;
int g[N][N];
int cost[L][N], dp[L][N];
int check(int x, int y);
int dfs(int s, int dep);
int calc(int s, int u);
int main() {
memset(g, -1, sizeof(g));
fill(cost[0], cost[L - 1] + N, -2);
fill(dp[0], dp[L - 1] + N, -2);
int m, u, v, w, ans = -1;
scanf("%d%d", &n, &m);
while (m--) {
scanf("%d%d%d", &u, &v, &w);
--u;
--v;
g[u][v] = g[v][u] = check(g[u][v], w);
}
U = (1 << n) - 1;
for (u = 0; u < n; ++u)
ans = check(ans, dfs(U ^ (1 << u), 1));
printf("%d\n", ans);
return 0;
}
inline int check(int x, int y) {
if (x == -1)
return y;
if (y == -1)
return x;
return min(x, y);
}
int dfs(int s, int dep) {
if (dp[s][dep] != -2)
return dp[s][dep];
if (!s)
return dp[s][dep] = 0;
dp[s][dep] = -1;
int cur;
for (int i = 0; i != s; ++i)
if ((i | s) == s) {
cur = dfs(i, dep + 1);
if (cur == -1)
continue;
for (int u = 0; u < n; ++u)
if (((1 << u) | (s ^ i)) == (s ^ i)) {
if (calc(U ^ s, u) == -1) {
cur = -1;
break;
}
cur += dep * cost[U ^ s][u];
}
dp[s][dep] = check(dp[s][dep], cur);
}
return dp[s][dep];
}
inline int calc(int s, int u) {
if (cost[s][u] != -2)
return cost[s][u];
if (!s)
return cost[0][u] = -1;
cost[s][u] = -1;
for (int v = 0; v < n; ++v)
if (((1 << v) | s) == s)
return cost[s][u] = check(g[u][v], calc(s ^ (1 << v), u));
return cost[s][u];
}
D2T3 列队
算法1
暴力swap
。
时间复杂度\Theta(q(n+m))Θ(q(n+m))。预计得分30\%30%。
算法2
思考n=1n=1的情况。
这时只是维护一个队列拿走一个元素以及从末尾插入一个元素。可以用平衡树维护。
推广该思想,一个n \times mn×m的方阵看做n+1n+1个队列。每行的前m-1m−1个人算一个队列,最后一行算一个队列。
时间复杂度\Theta(mn + q(\log n + \log m))Θ(mn+q(logn+logm))。空间复杂度\Theta(nm)Θ(nm)。预计得分50\%50%。
算法3
考虑将一段连续的区间抽象成一个节点。
时间复杂度\Theta(n + q(\log n + \log m))Θ(n+q(logn+logm))。空间复杂度\Theta(n + q)Θ(n+q)。预计得分100\%100%。
样例代码
#include <cstdio>
#include <ctime>
#include <algorithm>
#include <utility>
#define GET_CH(P, F) ((F) ? (P)->rs : (P)->ls)
#define LOG(FMT...) fprintf(stderr, FMT)
using namespace std;
typedef long long ll;
struct data {
int step, sz;
ll start;
data() {}
data(int step, int sz, ll start) : step(step), sz(sz), start(start) {}
data(ll x) : step(0), sz(1), start(x) {}
};
struct node {
int cnt, rnd;
data x;
node *ls, *rs;
};
struct que {
node* t;
void insert(ll x);
ll remove(int x);
};
const int N = 300010;
node *res1, *res2;
int resy;
que q1[N], q2;
int fast_rand();
node* create(const data& x);
int get_cnt(node* p);
void ninsert(node*& p, node* q, int x);
void rotate(node*& p, bool f);
void update(node* p);
pair<data, int> nremove(node*& p, int x);
void debug_order(node* p);
int main() {
int n, m, q, x, y;
ll id1, id2;
scanf("%d%d%d", &n, &m, &q);
for (int i = 1; i <= n; ++i)
q1[i].t = create(data(1, m - 1, (ll)(i - 1) * m));
q2.t = create(data(m, n, 0));
while (q--) {
scanf("%d%d", &x, &y);
if (y == m) {
id2 = q2.remove(x);
printf("%lld\n", id2);
q2.insert(id2);
} else {
id1 = q1[x].remove(y);
printf("%lld\n", id1);
id2 = q2.remove(x);
q1[x].insert(id2);
q2.insert(id1);
}
}
return 0;
}
inline int fast_rand() {
static int x = time(NULL) << 4, y = time(NULL) >> 2, z = time(NULL);
int t;
x ^= x << 16;
x ^= x >> 5;
x ^= x << 1;
t = x;
x = y;
y = z;
z = t ^ x ^ y;
return z;
}
inline node* create(const data& x) {
static node pool[N << 3];
static node* p = pool;
++p;
p->rnd = fast_rand();
p->x = x;
p->cnt = x.sz;
return p;
}
inline void rotate(node*& p, bool f) {
node *son = GET_CH(p, f), *pson = GET_CH(son, !f);
GET_CH(p, f) = pson;
GET_CH(son, !f) = p;
update(p);
p = son;
update(p);
}
inline int get_cnt(node* p)
{ return p ? p->cnt : 0; }
inline void update(node* p)
{ p->cnt = get_cnt(p->ls) + get_cnt(p->rs) + p->x.sz; }
void que::insert(ll x)
{ ninsert(t, create(data(x)), get_cnt(t) + 1); }
ll que::remove(int x) {
pair<data, int> p = nremove(t, x);
data d = p.first;
int y = p.second;
if (res2)
ninsert(t, res2, resy + 1);
if (res1)
ninsert(t, res1, resy + 1);
ll cur = (ll)(x - y) * d.step + d.start;
if (x - y < d.sz)
ninsert(t, create(data(d.step, d.sz - x + y, cur)), y + 1);
if (x - y > 1)
ninsert(t, create(data(d.step, x - y - 1, d.start)), y + 1);
return cur;
}
pair<data, int> nremove(node*& p, int x) {
pair<data, int> ret;
if (x <= get_cnt(p->ls)) {
ret = nremove(p->ls, x);
update(p);
return ret;
}
if (get_cnt(p->ls) + p->x.sz >= x) {
ret = make_pair(p->x, get_cnt(p->ls));
res1 = p->ls;
res2 = p->rs;
p = NULL;
resy = 0;
return ret;
}
ret = nremove(p->rs, x - get_cnt(p->ls) - p->x.sz);
resy += get_cnt(p->ls) + p->x.sz;
ret.second += get_cnt(p->ls) + p->x.sz;
update(p);
return ret;
}
void ninsert(node*& p, node* q, int x) {
if (!p) {
p = q;
return;
}
if (x <= get_cnt(p->ls) + p->x.sz) {
ninsert(p->ls, q, x);
update(p);
if (p->rnd < p->ls->rnd)
rotate(p, 0);
return;
}
ninsert(p->rs, q, x - get_cnt(p->ls) - p->x.sz);
update(p);
if (p->rnd < p->rs->rnd)
rotate(p, 1);
return;
}
void debug_order(node* p) {
if (!p)
return;
debug_order(p->ls);
LOG("(%lld %d %d) ", p->x.start, p->x.step, p->x.sz);
debug_order(p->rs);
}