A
同一起点如果有多个任务的话 最后走最短的那个 有几个走几次
然后把每个起点的贡献取一个max即可
在“会有跨越起点的情况”那里自闭了好久。。
但实际上每个点如果只走一次的话贡献的最远距离就是起点到它的距离+它要走的距离
#include <cstdlib>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <queue>
#include <vector>
#include <set>
#define sub(x, y) (x >= y ? x - y : x + n - y)
using namespace std;
const int N = 1e5 + 5;
int n, m;
int len[N];
long long cnt[N];
int main(){
scanf("%d%d", &n, &m);
memset(len, 0x3f, sizeof(len));
for(int i = 1, x, y; i <= m; ++i){
scanf("%d%d", &x, &y);
++cnt[x]; len[x] = min(len[x], sub(y, x));
}
for(int i = 1; i <= n; ++i) if(cnt[i]) cnt[i] = (cnt[i] - 1) * n; else cnt[i] = -1;
for(int i = 1; i <= n; ++i){
long long ans = 0;
for(int j = 1; j <= n; ++j) if(~cnt[j])
ans = max(ans, cnt[j] + len[j] + sub(j, i));
printf("%lld ", ans);
}
return 0;
}
B
怀疑这是假的。。
钦定数组长度为2000 设后面1999项的和为x
那么2000(x - 1) - 1999x = k
解得x = k
#include <cstdlib>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <queue>
#include <vector>
#include <set>
#define sub(x, y) (x >= y ? x - y : x + n - y)
using namespace std;
const int N = 1e6;
int main(){
int k; scanf("%d", &k); k += 2000;
printf("2000\n-1 ");
for(int i = 2; i <= 2000; ++i) printf("%d ", k > N ? N : k), k -= (k > N ? N : k);
return 0;
}
C
3000可以资瓷平方
求前缀的所有后缀嘛 那就用倒序的前缀字典树
这样每个点表示一个前缀的后缀
#include <cstdlib>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <queue>
#include <vector>
#include <set>
using namespace std;
const int N = 3005;
const long long P = 1e9 + 7;
int n, sz, a[N], ch[N * N][2], fa[N * N];
long long f[N * N], ans;
inline void add(long long &x, long long y){x += y; if(x >= P) x -= P;}
int main(){
scanf("%d", &n); sz = 1, f[1] = 1;
for(int i = 1; i <= n; ++i){
scanf("%d", &a[i]);
for(int j = i, cur = 1; j >= 1; cur = ch[cur][a[j]], --j){
if(ch[cur][a[j]]) continue;
++sz;
ch[cur][a[j]] = sz; fa[sz] = cur;
for(int k = 0, vv = cur, st = 0; k <= 3 && vv; ++k, vv = fa[vv]){
st = (st << 1) | a[j + k];
if(k < 3 || (st != 3 && st != 5 && st != 14 && st != 15)) add(f[sz], f[vv]);
}
add(ans, f[sz]);
}
printf("%lld\n", ans);
}
return 0;
}
D
来自zsy
description
求将{ai}分成若干段使每一段内都有恰好k个数出现了恰好一次的方案数模998244353。
solution
右端点从左往右扫,维护prei表示i前面最近的一个与ai相等数的位置(没有则为0),
每次将[prei+1,i]这段区间+1,将[preprei+1,prei]这段区间−1(如果prei≠0的话),
然后只要查前缀所有恰好等于k的位置的dp值之和就行了。
关于维护答案有一个点要注意 就是每次区间都是加一减一
所以每次整块也只会减去一个权值的贡献或加上一个权值的贡献[见*处]
#include <cstdlib>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <queue>
#include <vector>
#include <map>
using namespace std;
const int N = 1e5 + 5;
const int P = 998244353;
int n, m, a[N], pre[N], rec[N], f[N];
inline void add(int &x, int y){
x += y; if(x >= P) x -= P;
}
inline void sub(int &x, int y){
x -= y; if(x < 0) x += P;
}
struct BL{
int bel[N], tag[450], w[N], size, cnt, sum[450][N], ans[450];
void init(){
sum[1][0] = ans[1] = 1;
size = sqrt(n);
for(int i = 1; i <= n; ++i) bel[i] = (i - 1) / size + 1;
cnt = bel[n];
for(int i = 1; i <= cnt; ++i) tag[i] = m;
}
void update(int pos, int x){
int px = bel[pos];
sub(sum[px][w[pos]], f[pos - 1]);
if(w[pos] <= tag[px]) sub(ans[px], f[pos - 1]);
w[pos] = x;
add(sum[px][x], f[pos - 1]);
if(x <= tag[px]) add(ans[px], f[pos - 1]);
}
void ins(int x, int y, int d){
if(x > y) return;
if(bel[x] + 1 >= bel[y]){
for(int i = x; i <= y; ++i) update(i, w[i] + d);
return ;
}
for(int i = x; i <= bel[x] * size; ++i) update(i, w[i] + d);
for(int i = (bel[y] - 1) * size + 1; i <= y; ++i) update(i, w[i] + d);
for(int i = bel[x] + 1; i <= bel[y] - 1; ++i){
if(d > 0) sub(ans[i], sum[i][tag[i]]);
tag[i] -= d;
if(d < 0) add(ans[i], sum[i][tag[i]]);//*
}
}
int qry(int x){
int res = 0;
for(int i = x; bel[i] == bel[x]; --i) if(w[i] <= tag[bel[x]]) add(res, f[i - 1]);
for(int i = bel[x] - 1; i >= 1; --i) add(res, ans[i]);
return res;
}
}bl;
int main(){
scanf("%d%d", &n, &m); bl.init();
f[0] = 1;
for(int i = 1; i <= n; ++i){
scanf("%d", &a[i]);
pre[i] = rec[a[i]], rec[a[i]] = i;
bl.ins(pre[pre[i]] + 1, pre[i], -1), bl.ins(pre[i] + 1, i, 1);
f[i] = bl.qry(i);
add(bl.sum[bl.bel[i + 1]][0], f[i]);
add(bl.ans[bl.bel[i + 1]], f[i]);//记得这里要把下一位出现0次的贡献插入
}
printf("%d\n", f[n]);
return 0;
}
E
有一颗n个节点的树
每次可以询问两个点集S,T和一个点v,
询问有多少对\((s, t) (s∈S, t∈T)\)
使得v在(s,t)的路径上。
要求还原出这棵树的形态。
\(n≤500,Q \leq 11111\)
每次询问都变成1到一个点集 这样询问的就是到根路径
最初询问\(S = {1}, T = {2, 3, ..., n}, v = 2, 3, ..., n\)
这样得到每个点的size
按照size升序排列 删掉新加入点在已有集合的所有儿子
然后把它加到集合里
#include <cstdlib>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <queue>
#include <vector>
#include <set>
#define mp(x, y) make_pair(x, y)
using namespace std;
typedef pair<int, int> PII;
const int N = 505;
int n, m, id[N], size[N];
vector<int> vec, tot;
vector<PII> edge;
inline int query(vector<int> S, int x){
if(S.empty()) return 0;
printf("1\n1\n%d\n", S.size());
for(int i : S) printf("%d ", i);
printf("\n%d\n", x); fflush(stdout);
scanf("%d", &x); return x;
}
inline bool rule(int x, int y){
return size[x] < size[y];
}
int main(){
scanf("%d", &n); size[1] = n, id[1] = 1;
for(int i = 2; i <= n; ++i) tot.push_back(i), id[i] = i;
for(int i = 2; i <= n; ++i) size[i] = query(tot, i);
sort(id + 2, id + n + 1, rule);
for(int ii = 2, i = id[ii]; ii <= n; ++ii, i = id[ii]){
int j = query(vec, i);
while(j--){
int l = 0, r = vec.size() - 1, mid; vector<int> tmp;
while(l < r){
mid = l + ((r - l) >> 1);
tmp.clear(); for(int k = 0; k <= mid; ++k) tmp.push_back(vec[k]);
if(query(tmp, i)) r = mid;
else l = mid + 1;
}
edge.push_back(mp(i, vec[l]));
vec.erase(vec.begin() + l);//注意这里删除元素的方式
}
vec.push_back(i);
}
for(int i : vec) edge.push_back(mp(1, i));
printf("ANSWER\n");
for(PII i : edge) printf("%d %d\n", i.first, i.second);
return 0;
}
/*
5
4
2
1
1
0
1
1
2
1
*/