谢谢大家参与本次比赛~!
难度分配
简单题:ABCFH
中等题:DE
难题:GIJ
A
直接输入四个字符串,直接判断输出即可。无坑点
#include
#include
using namespace std;
int main(){
string a,b,c,d;
while(cin>>a>>b>>c>>d){
d.pop_back();
cout<<"I can "<
}
return 0;
}
B
用map实现每一个操作即可,具体看代码。如果不会map可以把每一个字符串hash成数字。
有一个坑点,就是在还没教会机器人的时候,问题就已经被禁止回答了,所以要用多个map去标记。
#include
#include
#include
using namespace std;
int main()
{
map
mp; map
vis; string a, b;
int Q;
scanf("%d", &Q);
while (Q--)
{
int t;
scanf("%d", &t);
if (t == 1)
{
cin >> a >> b;
mp[a] = b;
if (vis[a] == 0)
vis[a] = 1;
}
if (t == 2)
{
cin >> a;
if (mp[a] != "" && vis[a] == 1)
{
cout << mp[a] << endl;
}
else
{
cout << "I don't know what to say!" << endl;
}
}
if (t == 3)
{
cin >> a;
vis[a] = 2;
}
if (t == 4)
{
cin >> a;
vis[a] = 1;
}
}
return 0;
}
C
多写几项找规律得到公式
#include
using namespace std;
typedef long long ll;
int main()
{
ll N;
while (cin >> N)
{
if (N % 2 == 0)
cout << N * N + N / 2 << endl;
else
cout << N * N << endl;
}
return 0;
}
D
数据范围小,直接上Floyd求传递闭包。很经典的一道题目。离散数学有教。注意循环的顺序。
#include
using namespace std;
typedef long long ll;
bool G[305][305];
int main()
{
int N;
scanf("%d", &N);
for (int i = 1; i <= N; i++)
for (int j = 1; j <= N; j++)
scanf("%d", &G[i][j]);
for (int k = 1; k <= N; ++k)
{
for (int i = 1; i <= N; ++i)
{
for (int j = 1; j <= N; ++j)
{
if (G[i][j] == 1 || (G[i][k] == 1 && G[k][j] == 1))
{
G[i][j] = 1;
}
}
}
}
int Q;
scanf("%d", &Q);
while (Q--)
{
int u, v;
scanf("%d%d", &u, &v);
printf("%d\n", G[u][v]);
}
return 0;
}
E
很经典的线段树维护最大值,没有任何坑点。这里没有必要开四个线段树,具体看代码实现!
#include
#include
#include
using namespace std;
#define mod 10000007
typedef long long ll;
const int MAXN = 100005;
ll tree[MAXN << 2];
ll LI[4][MAXN];
void pushup(int rt)
{
tree[rt] = max(tree[rt << 1], tree[rt << 1 | 1]);
}
void update(int K, int C, int X, int l, int r, int rt)
{
if (l == r)
{
LI[K][C] = X;
tree[rt] = LI[0][C] * LI[1][C] * LI[2][C] * LI[3][C];
return;
}
int m = (l + r) / 2;
if (C <= m)
update(K, C, X, l, m, rt << 1);
else
update(K, C, X, m + 1, r, rt << 1 | 1);
pushup(rt);
}
ll query(int L, int R, int l, int r, int rt)
{
if (L <= l && r <= R)
{
return tree[rt];
}
ll ans = 0x3f3f3f3f3f3f3f3f;
ans = -ans;
int m = (l + r) / 2;
if (L <= m)
ans = max(ans, query(L, R, l, m, rt << 1));
if (R > m)
ans = max(ans, query(L, R, m + 1, r, rt << 1 | 1));
return ans;
}
int main()
{
int N, Q;
cin >> N >> Q;
while (Q--)
{
int t, K, C, X, L, R;
cin >> t;
if (t == 1)
{
cin >> K >> C >> X;
update(K - 1, C, X, 1, N, 1);
}
else
{
cin >> L >> R;
cout << query(L, R, 1, N, 1) << endl;
}
}
return 0;
}
F
暴力枚举即可,提取每一位,看看有没有重复数字,没有重复就输出。
#include
#include
using namespace std;
bool vis[10];
int num[3];
void solve(int n)
{
if (n == 3 && num[0] * 2 == num[1] && num[0] * 3 == num[2])
cout << num[0] << ' ' << num[1] << ' ' << num[2] << endl;
for (int i = 1; i <= 9; i++)
{
if (!vis[i])
{
vis[i] = true;
for (int j = 1; j <= 9; j++)
{
if (!vis[j])
{
vis[j] = true;
for (int k = 1; k <= 9; k++)
{
if (!vis[k])
{
vis[k] = true;
num[n] = i * 100 + j * 10 + k;
solve(n + 1);
vis[k] = false;
}
}
vis[j] = false;
}
}
vis[i] = false;
}
}
}
int main()
{
string a;
cin >> a;
memset(vis, false, sizeof(vis));
solve(0);
}
G
很经典的一道题目。考察二分+最小生成树
我们考虑将所有的白色的边加上一个cost,如x至y有一条白色边权值为len,那我们将它的权值变为cost+len,然后我们做最小生成树,很明显,如果加上的cost越大,所选的白色边越少,所以我们就二分这个cost,直至刚好选了need条时,则答案就是生成树的权值-need*cost。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define inf 1000000000
#define pa pair
#define ll long long
using namespace std;
int read()
{
int x = 0, f = 1;
char ch = getchar();
while (ch < '0' || ch > '9')
{
if (ch == '-')
f = -1;
ch = getchar();
}
while (ch >= '0' && ch <= '9')
{
x = x * 10 + ch - '0';
ch = getchar();
}
return x * f;
}
int n, m, cnt, tot, ned;
int sumv;
int u[100005], v[100005], w[100005], c[100005];
int fa[100005];
struct edge
{
int u, v, w, c;
} e[100005];
bool operator<(edge a, edge b)
{
return a.w == b.w ? a.c < b.c : a.w < b.w;
}
int find(int x)
{
return x == fa[x] ? x : fa[x] = find(fa[x]);
}
bool check(int x)
{
tot = cnt = 0;
for (int i = 1; i <= n; i++)
fa[i] = i;
for (int i = 1; i <= m; i++)
{
e[i].u = u[i], e[i].v = v[i], e[i].w = w[i];
e[i].c = c[i];
if (!c[i])
e[i].w += x;
}
sort(e + 1, e + m + 1);
for (int i = 1; i <= m; i++)
{
int p = find(e[i].u), q = find(e[i].v);
if (p != q)
{
fa[p] = q;
tot += e[i].w;
if (!e[i].c)
cnt++;
}
}
return cnt >= ned;
}
int main()
{
n = read();
m = read();
ned = read();
for (int i = 1; i <= m; i++)
{
u[i] = read(), v[i] = read(), w[i] = read(), c[i] = read();
u[i]++;
v[i]++;
}
int l = -105, r = 105;
while (l <= r)
{
int mid = (l + r) >> 1;
if (check(mid))
l = mid + 1, sumv = tot - ned * mid;
else
r = mid - 1;
}
printf("%d", sumv);
return 0;
}
H
模拟题。暴力枚举端点即可,但是不能用 N^3 的复杂度。第二个端点其实不用枚举,直接取中点即可,优化到了 N^2.
注意A 不能和B相等。像aaa这种输入就得输出SAD而不是a a a。
#include
#include
#include
#include
using namespace std;
#define mod 10000007
typedef long long ll;
char str[505];
int main()
{
int T;
scanf("%d", &T);
while (T--)
{
scanf("%s", str);
int len = strlen(str);
bool yes = 1;
for (int i = 1; i <= len - 2; i++)
{
if ((len - i) % 2 == 1)
continue;
string a, b, c;
for (int k = 0; k < i; k++)
a.push_back(str[k]);
for (int k = i; k < i + (len - i) / 2; k++)
b.push_back(str[k]);
for (int k = i + (len - i) / 2; k < len; k++)
c.push_back(str[k]);
if (a != b && b == c)
{
cout << a << " " << b << " " << c << endl;
yes = 0;
break;
}
}
if (yes)
cout << "SAD" << endl;
}
return 0;
}
I
数学题
要推出任意次方和公式
我们会发现,求k次和的时候,需要用到k-1,k-2…,0的结果。
所以实际求的时候,可以反过来逐个计算。
组合数我们可以事先计算好,除以k+1,也可以预处理乘法逆元。
所以总的复杂度O(k^2)。
#include
using namespace std;
typedef long long LL;
const int mod = 1e9 + 7;
const int N = 2050;
int T, y;
LL x, C[N][N], rev[N], dp[N];
LL pow_mod(LL x, LL p)
{
LL s = 1;
while (p)
{
if (p & 1)
s = s * x % mod;
x = x * x % mod;
p >>= 1;
}
return s;
}
void init()
{
rev[0] = 1;
C[0][0] = 1;
for (int i = 1; i < N; i++)
{
C[i][0] = C[i][i] = 1;
for (int j = 1; j < i; j++)
{
C[i][j] = (C[i - 1][j - 1] + C[i - 1][j]) % mod;
}
rev[i] = pow_mod(i, mod - 2);
}
}
LL solve1(LL x, int y)
{
x = x % mod;
dp[0] = x;
LL n = (x + 1) % mod;
for (int i = 1; i <= y; i++)
{
dp[i] = (pow_mod(n, i + 1) + mod - 1) % mod;
for (int j = 1; j <= i; j++)
{
dp[i] = (dp[i] - C[i + 1][j + 1] * dp[i - j] % mod) % mod;
}
if (dp[i] < 0)
dp[i] += mod;
dp[i] = dp[i] * rev[i + 1] % mod;
}
return dp[y];
}
LL solve2(int x, LL y)
{
LL ret = 0;
for (int i = 1; i <= x; i++)
{
ret = (ret + pow_mod(i, y)) % mod;
}
return ret;
}
int main()
{
init();
scanf("%d", &T);
while (T--)
{
scanf("%lld %d", &x, &y);
printf("%lld\n", (solve1(x, y) + solve2(y, x)) % mod);
}
return 0;
}
J
一道防AK题
做法是 主席树+字典树+二分
我们先把所有ADD的字符串插入到一颗字典树。实际上每一次查询,就是查询字典树某个子树中某一层的节点的权值和。
首先我们可以用dfs序给字典树每一个节点编号记为dfn,然后再做一次宽搜,给每一个节点再编一次编号记为bfn。
然后我们记录字典树每一层的节点都有哪些。
然后我们再用把树拍扁的思想,记录每一个节点所代表的区间。然后建一棵线段树。
然后对于ADD操作,我们直接找到那个字符串在字典树中的最后一个节点,对那个bfn所在地方+1,用线段树维护和。
然后对于QUERY操作,我们直接找到那个前缀所在的节点的那个子树,然后要找到,所要查询那个长度所在的层的哪些编号属于那个子树。
对于这个需求,我们直接对那一层的节点二分,二分值是节点所代表的的区间。这样子我们就能确定那一层哪些节点属于那个子树了。
对于回退问题,我们直接用可持久化线段树解决
额外的的解释:
全部读入,构建字典树,记录下每个ADD和QUERY的字符串末尾对应的节点ID;
对字典树做dfs
记录dfs序,dfs_lft[x]和dfs_rgt[x]分别表示以x为根的子树中的最小dfs序和最大dfs序;
将深度相同的节点放到一个数组,按dfs序升序排序,同时记录对应的节点ID;
按照节点深度和dfs序重新排列,即第一层所有节点的dfs序,第二层所有节点的dfs序…
同时记录每个节点跟这个新序列的映射关系idx[x];
对这个序列构建主席树;
对于ADD操作,主席树开新版本,找到对应的idx进行加1;
对于REVERT操作,直接将当前版本指向指定版本;
对于QUERY操作,先找到对应的子树根节点x,根据dfs_lft[x]和dfs_rgt[x],二分出在相应深度的dfs序,因为在x的子节点的dfs序必定大于等于dfs_lft[x],小于等于dfs_rgt[x],而非其子节点必定不满足此条件。根据这个dfs序范围反查出两端节点ID,进而转化为主席树的区间端点,然后进行简单的区间求和操作即可。
标程1:
#include
using namespace std;
const int N = 1e5 + 10;
int T, Q;
char op[10], ss[N];
int ch[N][26], oper[N], num[N], dep[N], node_id[N];
int sz, max_dep, dfs_cnt, dfs_lft[N], dfs_rgt[N], idx[N];
vector
V[N], G[N]; int n, node_cnt, tree[N], S[N * 20], lson[N * 20], rson[N * 20];
struct Trie
{
void init()
{
sz = 1;
memset(ch[0], 0, sizeof(ch[0]));
}
int add()
{
int len = strlen(ss);
int u = 0, c;
for (int i = 0; i < len; i++)
{
c = ss[i] - 'a';
if (!ch[u][c])
{
memset(ch[sz], 0, sizeof(ch[sz]));
ch[u][c] = sz++;
}
u = ch[u][c];
}
return u;
}
void dfs(int u)
{
max_dep = max(max_dep, dep[u]);
dfs_lft[u] = ++dfs_cnt;
V[dep[u]].push_back(dfs_cnt);
G[dep[u]].push_back(u);
for (int i = 0; i < 26; i++)
{
if (ch[u][i])
{
dep[ch[u][i]] = dep[u] + 1;
dfs(ch[u][i]);
}
}
dfs_rgt[u] = dfs_cnt;
}
} tr;
int build(int L, int R)
{
int ret = ++node_cnt;
S[ret] = 0;
if (L < R)
{
int mid = L + R >> 1;
lson[ret] = build(L, mid);
rson[ret] = build(mid + 1, R);
}
else
{
lson[ret] = rson[ret] = 0;
}
return ret;
}
int add_node(int root0, int p, int v)
{
int L = 1, R = n, mid;
int root = ++node_cnt;
int ret = root;
S[root] = S[root0] + v;
while (L < R)
{
mid = L + R >> 1;
if (p <= mid)
{
lson[root] = ++node_cnt;
rson[root] = rson[root0];
root = lson[root];
root0 = lson[root0];
R = mid;
}
else
{
lson[root] = lson[root0];
rson[root] = ++node_cnt;
root = rson[root];
root0 = rson[root0];
L = mid + 1;
}
S[root] = S[root0] + v;
}
return ret;
}
int sum(int root, int L, int R, int ll, int rr)
{
if (L == ll && R == rr)
{
return S[root];
}
int mid = L + R >> 1;
if (rr <= mid)
return sum(lson[root], L, mid, ll, rr);
if (ll > mid)
return sum(rson[root], mid + 1, R, ll, rr);
return sum(lson[root], L, mid, ll, mid) + sum(rson[root], mid + 1, R, mid + 1, rr);
}
int query(int root, int node, int len)
{
if (len > max_dep || dep[node] > len)
return 0;
if (V[len].size() == 0)
return 0;
int lft = dfs_lft[node];
int rgt = dfs_rgt[node];
if (rgt < V[len][0] || lft > V[len].back())
return 0;
int low, top, mid, key_l, key_r;
low = 0;
top = V[len].size() - 1;
key_l = top;
while (low <= top)
{
mid = low + top >> 1;
if (V[len][mid] >= lft)
{
key_l = min(key_l, mid);
top = mid - 1;
}
else
{
low = mid + 1;
}
}
low = 0;
top = V[len].size() - 1;
key_r = 0;
while (low <= top)
{
mid = low + top >> 1;
if (V[len][mid] <= rgt)
{
key_r = max(key_r, mid);
low = mid + 1;
}
else
{
top = mid - 1;
}
}
if (key_l > key_r)
return 0;
int idx_lft = idx[G[len][key_l]];
int idx_rgt = idx[G[len][key_r]];
return sum(root, 1, n, idx_lft, idx_rgt);
}
void init()
{
dfs_cnt = 0;
max_dep = 0;
dep[0] = 0;
tr.dfs(0);
n = 0;
for (int i = 1; i <= max_dep; i++)
{
for (int j = 0; j < G[i].size(); j++)
{
idx[G[i][j]] = ++n;
}
}
node_cnt = 0;
tree[0] = build(1, n);
}
int main()
{
scanf("%d", &Q);
tr.init();
for (int i = 1; i <= Q; i++)
{
scanf("%s", op);
if (op[0] == 'A')
{
scanf("%s", ss);
node_id[i] = tr.add();
oper[i] = 0;
}
else if (op[0] == 'Q')
{
scanf("%s %d", ss, num + i);
node_id[i] = tr.add();
oper[i] = 1;
}
else
{
scanf("%d", num + i);
oper[i] = 2;
}
}
init();
for (int i = 1; i <= Q; i++)
{
if (oper[i] == 0)
{
int j = idx[node_id[i]];
tree[i] = add_node(tree[i - 1], j, 1);
}
else if (oper[i] == 1)
{
tree[i] = tree[i - 1];
printf("%d\n", query(tree[i], node_id[i], num[i]));
}
else
{
tree[i] = tree[num[i] - 1];
}
}
for (int i = 0; i <= max_dep; i++)
{
V[i].clear();
G[i].clear();
}
return 0;
}
标程2:
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
#define MAXN 101005
typedef long long ll;
int nxt[MAXN][28];
int cnt = 0;
void insert(string str)
{
int len = str.size();
int cur = 0;
for (int i = 0; i < len; i++)
{
if (nxt[cur][str[i] - 'a'] == 0)
nxt[cur][str[i] - 'a'] = ++cnt;
cur = nxt[cur][str[i] - 'a'];
}
}
int getid(string str)
{
int len = str.size();
int cur = 0;
for (int i = 0; i < len; i++)
{
if (nxt[cur][str[i] - 'a'] == 0)
return 0;
cur = nxt[cur][str[i] - 'a'];
}
return cur;
}
int ldfn[MAXN];
int rdfn[MAXN];
int mytime = 0;
void dfs(int cur)
{
ldfn[cur] = ++mytime;
for (int i = 0; i < 26; i++)
{
if (nxt[cur][i])
{
dfs(nxt[cur][i]);
}
}
rdfn[cur] = mytime;
}
int bfn[MAXN];
vector
ceng[MAXN * 2]; int bt = 1;
void bfs()
{
queue
> que; que.push({0, 0});
while (!que.empty())
{
pair
tp = que.front(); que.pop();
ceng[tp.first].push_back(tp.second);
for (int i = 0; i < 27; i++)
{
if (nxt[tp.second][i])
{
que.push({tp.first + 1, nxt[tp.second][i]});
}
}
}
}
int root[MAXN];
int mycount = 0;
int tree[MAXN * 15];
int rs[MAXN * 15];
int ls[MAXN * 15];
void update(int C, int X, int l, int r, int &rt, int lrt)
{
rt = ++mycount;
ls[rt] = ls[lrt];
rs[rt] = rs[lrt];
tree[rt] = tree[lrt] + X;
if (l == r)
{
return;
}
int m = (l + r) / 2;
if (C <= m)
update(C, X, l, m, ls[rt], ls[lrt]);
else
update(C, X, m + 1, r, rs[rt], rs[lrt]);
}
int query(int L, int R, int l, int r, int rt)
{
if (L <= l && r <= R)
{
return tree[rt];
}
int m = (l + r) / 2;
int ans = 0;
if (L <= m)
ans += query(L, R, l, m, ls[rt]);
if (R > m)
ans += query(L, R, m + 1, r, rs[rt]);
return ans;
}
struct queryq
{
int type;
string str;
int len;
} q[100505];
char str[50];
char tmp[100005];
int main()
{
int Q;
scanf("%d", &Q);
for (int i = 1; i <= Q; i++)
{
scanf("%s", str);
if (str[0] == 'A')
{
scanf("%s", tmp);
q[i].type = 0;
q[i].str = tmp;
q[i].len = 0;
insert(tmp);
}
else if (str[0] == 'Q')
{
scanf("%s", tmp);
int len;
scanf("%d", &len);
q[i].type = 1;
q[i].str = tmp;
q[i].len = len;
}
else
{
int len;
scanf("%d", &len);
q[i].type = 2;
q[i].str = "";
q[i].len = len;
}
}
dfs(0);
bfs();
for (int i = 0; i <= 100005; i++)
{
for (int j = 0; j < ceng[i].size(); j++)
{
bfn[ldfn[ceng[i][j]]] = bt++;
ceng[i][j] = ldfn[ceng[i][j]];
}
}
cnt++;
for (int i = 1; i <= Q; i++)
{
if (q[i].type == 0)
{
int tp = getid(q[i].str);
update(bfn[ldfn[tp]], 1, 1, cnt, root[i], root[i - 1]);
}
else if (q[i].type == 1)
{
root[i] = root[i - 1];
int tp = getid(q[i].str);
if (tp == 0)
{
printf("0\n");
continue;
}
if (q[i].len < q[i].str.size())
{
printf("0\n");
continue;
}
if (q[i].len > 100005)
{
printf("0\n");
continue;
}
if (ceng[q[i].len].size() == 0)
{
printf("0\n");
continue;
}
if (ldfn[tp] > ceng[q[i].len].back())
{
printf("0\n");
continue;
}
if (rdfn[tp] < ceng[q[i].len].front())
{
printf("0\n");
continue;
}
int l;
int r;
if (ldfn[tp] <= ceng[q[i].len].front())
l = ceng[q[i].len].front();
else
l = *lower_bound(ceng[q[i].len].begin(), ceng[q[i].len].end(), ldfn[tp]);
if (rdfn[tp] >= ceng[q[i].len].back())
r = ceng[q[i].len].back();
else
r = *(upper_bound(ceng[q[i].len].begin(), ceng[q[i].len].end(), rdfn[tp]) - 1);
printf("%d\n", query(bfn[l], bfn[r], 1, cnt, root[i]));
}
else
{
root[i] = root[q[i].len - 1];
}
}
return 0;
}