2020集训队作业选做part3
CF 666E
Solution
首先对\(T_i\)建出广义SAM,预处理出\(S\)的前缀\(S[1...r]\)S在SAM里能匹配上的最长后缀的长度以及它匹配到的位置,每次查询的时候从\(S[1...r]\)匹配到的位置开始,在parent树上倍增,找到\(len\)最小的节点,使得\(len_u \geq r-l+1\),于是\(u\)的子树中的所有节点表示的子串都包含了\(S[l...r]\),通过线段树合并就变成了子树数颜色以及找到最多颜色的问题了。
时间复杂度\(\mathcal O(n\log n)\)
Code
int n,m,Q,Test;
int rt[MAXN],len[MAXN];
char s[MAXM],t[MAXN];
struct Node{
int x,y;
Node(int _x = 0,int _y = 0) : x(_x), y(_y) {}
bool operator < (const Node &a) const{
return x == a.x ? y > a.y : x < a.x;
}
};
struct SegmentTree{
static const int MAXL = MAXN * 30;
int tot;
int ls[MAXL],rs[MAXL];
Node val[MAXL];
void update(int k){
val[k] = max(val[ls[k]],val[rs[k]]);
}
void modify(int &k,int l,int r,int x){
if(!k) k = ++tot;
if(l == r) {val[k].x += 1; val[k].y = l; return;}
int mid = (l + r) >> 1;
if(x <= mid) modify(ls[k],l,mid,x);
else modify(rs[k],mid + 1,r,x);
update(k);
}
Node query(int k,int l,int r,int ql,int qr){
if(!k) return Node(0,0);
if(l >= ql && r <= qr) return val[k];
int mid = (l + r) >> 1; Node res(0,0);
if(ql <= mid) checkMax(res,query(ls[k],l,mid,ql,qr));
if(qr > mid) checkMax(res,query(rs[k],mid + 1,r,ql,qr));
return res;
}
int merge(int x,int y,int l,int r){
if(!x || !y) return x | y;
int k = ++tot;
if(l == r){
val[k] = val[x];
val[k].x += val[y].x;
return k;
}
int mid = (l + r) >> 1;
ls[k] = merge(ls[x],ls[y],l,mid);
rs[k] = merge(rs[x],rs[y],mid + 1,r);
update(k); return k;
}
} T;
struct SAM{
int last,tot;
int son[MAXN][26],len[MAXN],fa[MAXN],maxlen[MAXM],lstpos[MAXM],f[MAXN][LOG];
vector<int> G[MAXN];
SAM() {last = tot = 1;}
void extend(int c,int id){
static int p,q,np,nq;
p = last;
if(son[p][c]){
q = son[p][c];
if(len[q] == len[p] + 1) {last = q; return;}
nq = ++tot; len[nq] = len[p] + 1;
memcpy(son[nq],son[q],sizeof(son[q]));
fa[nq] = fa[q]; fa[q] = nq;
for(;p && son[p][c] == q;p = fa[p]) son[p][c] = nq;
last = nq; return;
}
last = ++tot; np = tot; len[np] = len[p] + 1;
for(;p && !son[p][c];p = fa[p]) son[p][c] = np;
if(!p) {fa[np] = 1; return;}
q = son[p][c];
if(len[q] == len[p] + 1) {fa[np] = q; return;}
nq = ++tot; memcpy(son[nq],son[q],sizeof(son[nq]));
len[nq] = len[p] + 1;
fa[nq] = fa[q]; fa[q] = fa[np] = nq;
for(;p && son[p][c] == q;p = fa[p]) son[p][c] = nq;
}
void init(){
for(int i = 2;i <= tot;i++) G[fa[i]].push_back(i);
int p = 1, l = 0;
for(int i = 1;i <= n;i++){
int c = (s[i] - 'a');
while(p && !son[p][c]) p = fa[p], l = len[p];
if(son[p][c]) p = son[p][c], l += 1;
else p = 1, l = 0;
lstpos[i] = p; maxlen[i] = l;
}
}
void dfs(int u,int father){
f[u][0] = father;
for(int i = 1;i < LOG;i++) f[u][i] = f[f[u][i - 1]][i - 1];
for(int v : G[u]){
dfs(v,u);
rt[u] = T.merge(rt[u],rt[v],1,m);
}
}
Node query(int l,int r,int pl,int pr){
if(pr - pl + 1 > maxlen[pr]) return {0,l};
int p = lstpos[pr];
for(int i = LOG - 1;i >= 0;i--)
if(len[f[p][i]] >= pr - pl + 1) p = f[p][i];
Node res = T.query(rt[p],1,m,l,r);
if(!res.x) res.y = l;
return res;
}
} sam;
int main(){
read(s + 1); read(m); n = strlen(s + 1);
for(int i = 1;i <= m;i++){
read(t + len[i - 1]); sam.last = 1;
len[i] = len[i - 1] + strlen(t + len[i - 1]);
for(int j = len[i - 1];j < len[i];j++) sam.extend(t[j] - 'a',i);
}
for(int i = 1;i <= m;i++){
int p = 1;
for(int j = len[i - 1];j < len[i];j++)
p = sam.son[p][t[j] - 'a'], T.modify(rt[p],1,m,i);
}
sam.init(); sam.dfs(1,0);
read(Q);
while(Q--){
Test += 1;
int l,r,pl,pr; read(l,r,pl,pr);
Node ans = sam.query(l,r,pl,pr);
printf("%d %d\n",ans.y,ans.x);
}
return 0;
}
CF 568C
Solution
不难发现这是一个2−SAT问题,我们从后往前枚举第一个大于原串的位置,然后暴力染色判断是否合法即可。
时间复杂度\(\mathcal O(nm)\)
Code
int n,m,l,top;
int a[MAXN],nxt[MAXN][2],vis[MAXN],st[MAXN * MAXN];
char s[MAXN];
vector<int> G[MAXN];
void add_edge(int u,int v){
G[u].push_back(v);
}
int opp(int x) {return x > n ? x - n : x + n;}
bool dfs(int u){
if(vis[opp(u)]) return false;
vis[u] = 1; st[++top] = u;
for(int v : G[u]) if(!vis[v] && !dfs(v)) return false;
return true;
}
bool check(int p){
for(int i = 1;i <= n + n;i++) vis[i] = 0;
for(int i = 1;i <= p;i++)
if(!dfs(i + a[s[i] - 'a' + 1] * n)) return false;
for(int i = p + 1;i <= n;i++){
if(vis[i]) s[i] = nxt[1][0] + 'a' - 1;
else if(vis[i + n]) s[i] = nxt[1][1] + 'a' - 1;
else{
int x = min(nxt[1][0],nxt[1][1]), y = max(nxt[1][0],nxt[1][1]); top = 0;
if(dfs(i + a[x] * n)) s[i] = x + 'a' - 1;
else{
while(top) vis[st[top--]] = 0;
if(dfs(i + a[y] * n)) s[i] = y + 'a' - 1;
else return false;
}
}
}
return true;
}
int main(){
scanf("%s",s + 1); l = strlen(s + 1);
nxt[l + 1][0] = nxt[l + 1][1] = l + 1;
for(int i = l, lst[2] = {l + 1,l + 1};i >= 1;i--){
a[i] = (s[i] == 'C'); lst[a[i]] = i;
nxt[i][0] = lst[0]; nxt[i][1] = lst[1];
}
scanf("%d%d",&n,&m);
for(int i = 1,x,t1,t2;i <= m;i++){
scanf("%d%s",&x,s + 1);
t1 = x + (s[1] == 'C') * n;
scanf("%d%s",&x,s + 1);
t2 = x + (s[1] == 'C') * n;
add_edge(t1,t2); add_edge(opp(t2),opp(t1));
}
scanf("%s",s + 1); if(check(n)) return printf("%s\n",s + 1), 0;
else if(nxt[1][0] == l + 1 || nxt[1][1] == l + 1) return printf("-1\n"), 0;
for(int i = n;i >= 1;i--){
int c = s[i] - 'a' + 2;
int x = min(nxt[c][0],nxt[c][1]), y = max(nxt[c][0],nxt[c][1]);
if(x != l + 1){
s[i] = x + 'a' - 1;
if(check(i)) return printf("%s\n",s + 1), 0;
}
if(y != l + 1){
s[i] = y + 'a' - 1;
if(check(i)) return printf("%s\n",s + 1), 0;
}
}
printf("-1\n");
return 0;
}
CF 585E
Solution
提供一种只用做一次狄利克雷前缀和的做法。
考虑枚举\(gcd\)的值,设为\(d\),记\(c_i\)为\(i\)的倍数的个数,那么\(d\)对答案的贡献为\((n-c_d)\times (2^{c_d}-1)\),但这样显然会算重,考虑容斥,不难发现容斥系数恰好为\(-\mu (d)\)。
下面考虑如何对于每个\(i\)求出\(c_i\),暴力计算是调和级数\(\mathcal O(w\log w)\)的,但这样显然不够优秀。
考虑令\(cnt_i\)为恰好等于\(i\)的数的个数,那么\(c_i=\sum_{i|j}cnt_j\),不难发现这是狄利克雷后缀和的形式,直接把狄利克雷前缀和反过来做即可。
时间复杂度\(\mathcal O(w \log\log w)\)
Code
int n,tot;
int p[MAXM],a[MAXN],mu[MAXM],c[MAXM],bin[MAXN];
bool np[MAXM];
void Init(int n){
for(int i = 2;i <= n;i++){
if(!np[i]) p[++tot] = i, mu[i] = -1;
for(int j = 1;j <= tot;j++){
if(i * p[j] > n) break;
np[i * p[j]] = 1;
if(i % p[j] == 0) {mu[i * p[j]] = 0; break;}
mu[i * p[j]] = -mu[i];
}
}
}
int main(){
read(n); for(int i = 1;i <= n;i++) read(a[i]), c[a[i]] += 1;
bin[0] = 1; for(int i = 1;i <= n;i++) bin[i] = 2ll * bin[i - 1] % MOD;
int m = *max_element(a + 1,a + 1 + n); Init(m);
for(int i = 1;i <= tot;i++)
for(int j = m / p[i];j >= 1;j--)
c[j] += c[j * p[i]];
int ans = 0;
for(int i = 1;i <= m;i++)
ans = (ans - (ll)mu[i] * (bin[c[i]] - 1) * (n - c[i])) %MOD;
ans = (ans + MOD) % MOD;
printf("%d\n",ans);
return 0;
}
CF 521D
Solutionn
考虑如果只有乘法,那一定是把操作数字从大到小排序,依次操作。
考虑操作顺序,最优的操作一定是先赋值,再加法,最后乘法。其中赋值只可能选相应位置最大的赋值,加法也是从大到小考虑。
显然赋值可以直接转化成加法,而加法如果从大到小考虑也可以直接转化成乘法(乘一个实数)。
这样就把所有操作转化成了乘法。从大到小贪心考虑即可。
时间复杂度\(\mathcal O(n\log n)\)
Code
int k,n,m;
int a[MAXN],t[MAXN];
pii assign[MAXN];
vector<pii> add[MAXN];
vector<pair<double,int> > mul;
int main(){
scanf("%d%d%d",&n,&m,&k);
for(int i = 1;i <= n;i++) scanf("%d",&a[i]);
for(int i = 1,x,b;i <= m;i++){
scanf("%d%d%d",t + i,&x,&b);
if(t[i] == 1) checkMax(assign[x],make_pair(b,i));
if(t[i] == 2) add[x].emplace_back(b,i);
if(t[i] == 3) mul.emplace_back(b,i);
}
for(int i = 1;i <= n;i++)
if(assign[i].first > a[i])
add[i].emplace_back(assign[i].first - a[i],assign[i].second);
for(int i = 1;i <= n;i++){
sort(add[i].begin(),add[i].end()); reverse(add[i].begin(),add[i].end());
ll v = a[i];
for(auto p : add[i]){
mul.emplace_back(1.0 * (v + p.first) / v,p.second);
v += p.first;
}
}
sort(mul.begin(),mul.end()); reverse(mul.begin(),mul.end());
int num = min(k,(int)mul.size());
sort(mul.begin(),mul.begin() + num,[&](const auto &x, const auto &y) {return t[x.second] < t[y.second];});
printf("%d\n",num);
for(int i = 0;i < num;i++) printf("%d ",mul[i].second);
return 0;
}
CF 585F
Solution
考虑用数位 DP 来统计数字串个数,用 SAM 来实现子串的匹配。
设\(dp(i,p,len,limit,flag)\)表示考虑到了第\(i\)位,在 SAM 上匹配到了节点\(p\),匹配长度是\(len\),是否达到上界,匹配长度是否大于\(\lfloor\frac{d}{2}\rfloor\)的方案数,转移的时候枚举当前数位上的数\(c\),若\(p\)能识别\(c\),则直接转移,否则暴力的在 parent 树上跳父亲直到能识别\(c\)位置。
时间复杂度\(\mathcal O(10nd^2)\)
Code
int need;
char a[MAXN],b[MAXN],s[MAXN];
struct SAM{
int tot,last,cnt;
int fa[MAXN],len[MAXN],son[MAXN][10],dp[MAXM][MAXN][MAXM][2],a[MAXM];
SAM() {tot = last = 1;}
void extend(int c){
static int p,q,np,nq;
p = last; np = last = ++tot;
len[np] = len[p] + 1;
for(;p && !son[p][c];p = fa[p]) son[p][c] = np;
if(!p) {fa[np] = 1; return;}
q = son[p][c];
if(len[q] == len[p] + 1) {fa[np] = q; return;}
nq = ++tot; memcpy(son[nq],son[q],sizeof(son[nq]));
len[nq] = len[p] + 1; fa[nq] = fa[q]; fa[q] = fa[np] = nq;
for(;p && son[p][c] == q;p = fa[p]) son[p][c] = nq;
}
void build(char *s){
int n = strlen(s + 1);
for(int i = 1;i <= n;i++) extend(s[i] - '0');
}
int dfs(int d,int pos,int l,bool limit,bool flag){
if(d == cnt + 1) return flag;
if(!limit && dp[d][pos][l][flag] != -1) return dp[d][pos][l][flag];
int &v= dp[d][pos][l][flag]; v = 0;
int up = limit ? a[d] : 9;
for(int i = 0;i <= up;i++){
if(flag) {addmod(v,dfs(d + 1,pos,l,limit && i == up,1)); continue;}
int p = pos;
if(son[p][i]) {addmod(v,dfs(d + 1,son[p][i],l + 1,limit && i == up,l + 1 >= need)); continue;}
while(p && !son[p][i]) p = fa[p];
if(!p) addmod(v,dfs(d + 1,1,0,limit && i == up,0));
else addmod(v,dfs(d + 1,son[p][i],len[p] + 1,limit && i == up,len[p] + 1 >= need));
}
return v;
}
int solve(char *s){
cnt = strlen(s + 1); memset(dp,-1,sizeof(dp));
for(int i = 1;i <= cnt;i++) a[i] = s[i] - '0';
return dfs(1,1,0,1,0);
}
} sam;
int main(){
scanf("%s%s%s",s + 1,a + 1,b + 1); sam.build(s);
int n = strlen(a + 1); need = n / 2;
for(int i = n;i >= 1;i--){
if(a[i] - 1 < '0') a[i] = '9';
else {a[i] -= 1; break;}
}
printf("%d\n",sub(sam.solve(b),sam.solve(a)));
return 0;
}
CF 671E
Solution
神仙题,前面转化的部分参见粉兔的博客,这里只讲一下如何用线段树维护抽象后的模型。
给出两个长度为\(n\)的序列\(a,b\),令\(c_i=a_i+\max_{j=1}^i\{b_j\}\)
需要支持对\(b_i\)的区间加减操作和询问满足\(c_i\leq k\)的最大的\(i\),其中\(k\)为定值。
对于询问,相当于在线段树上二分,所以我们需要维护\(c\)的区间最小值。
考虑在线段树上维护一下三个信息:
- 当前区间中\(a_i\)的最小值\(mina\)
- 当前区间中\(b_i\)的最大值\(maxb\)
- 仅考虑当前区间中的\(a_i\)和\(b_i\)时,右儿子区间中最小的\(c_i\),记为\(ans\)
考虑如何 pushup ,定义一个函数\(\mathrm{calc}(i,pre)\)表示前缀最小值为\(pre\)时\(i\)对应的区间内\(c_i\)的最小值。那么这个区间的\(ans\)就等于\(\mathrm{calc}(rson,\mathrm{maxb}[lson])\)。\(\mathrm{calc}(i,pre)\)的伪代码如下:
- 对于当\(i\)是叶节点的情况显然。
- 假如\(pre < {\mathrm{maxb}}[\mathrm{lson}[i]]\),那么对右子树来说直接继承答案即可,然后递归进左子树。
- 否则左子树中所有的\(b_i\)都\(\leq pre\)那么当前区间内的\(b\)的前缀最大值也自然是都等于\(pre\),只要考虑\(a_i\)的最小值即可。
考虑如何查询答案,这里跟普通的线段树上二分有些区别,定义函数\(\mathrm{query}(i,pre)\)表示\(c_i\)的前缀最大值为\(pre\)时,线段树中节点\(i\)对应区间中满足\(c_i\leq k\)的最大的\(i\)。
- 若\({\mathrm{maxb}}[\mathrm{lson}[i]] > pre\),相当于\(pre\)影响不到右子树,那么如果\(\mathrm{ans}\leq k\)就递归进入右子树,否则进入左子树。
- 若\({\mathrm{maxb}}[\mathrm{lson}[i]] \leq pre\),相当于左子树中前缀最大值等于\(pre\),先递归进入右子树查询,如果没查询到,则考虑左子树。因为左子树中前缀最大值等于\(pre\),所以限制变为\(a_i + pre \le k\)。移项得到\(a_i \le k - pre\),这是一个常规的线段树上二分。
因为只会进行\(\mathcal O (\log n)\)次线段树上二分,所以时间复杂度为\(\mathcal O (\log^2 n)\)。
Code
int n,K,len,top;
ll ans;
int a[MAXN],w[MAXN],head[MAXN],nxt[MAXN],st[MAXN];
ll pre[MAXN],suf[MAXN];
struct Edge{
int to,next;
} e[MAXN << 1];
void add_edge(int u,int v){
e[++len] = (Edge){v,head[u]};
head[u] = len;
}
struct SegmentTree{
struct Tree{
int l,r;
ll mina,minb,ans,tag;
} tree[MAXN << 2];
#define lson k << 1
#define rson k << 1 | 1
void apply(int k,ll v) {tree[k].tag += v; tree[k].minb += v; tree[k].ans -= v;}
void pushdown(int k){
if(tree[k].tag){
apply(lson,tree[k].tag);
apply(rson,tree[k].tag);
tree[k].tag = 0;
}
}
ll calc(int k,ll pre){
if(tree[k].l == tree[k].r) return tree[k].mina - pre;
pushdown(k);
if(pre > tree[lson].minb) return min(calc(lson,pre),tree[k].ans);
else return min(calc(rson,pre),tree[lson].mina - pre);
}
void update(int k){
tree[k].mina = min(tree[lson].mina,tree[rson].mina);
tree[k].minb = min(tree[lson].minb,tree[rson].minb);
tree[k].ans = calc(rson,tree[lson].minb);
}
void build(int k,int l,int r){
tree[k].l = l; tree[k].r = r;
if(l == r) {tree[k].mina = tree[k].minb = suf[r]; return;}
int mid = (l + r) >> 1;
build(lson,l,mid); build(rson,mid + 1,r);
update(k);
}
void modify(int k,int l,int r,ll v){
if(tree[k].l >= l && tree[k].r <= r) {apply(k,v); return;}
int mid = (tree[k].l + tree[k].r) >> 1; pushdown(k);
if(l <= mid) modify(lson,l,r,v);
if(r > mid) modify(rson,l,r,v);
update(k);
}
ll findpos(int k,ll x){
if(tree[k].l == tree[k].r) return tree[k].l;
return tree[rson].mina <= x ? findpos(rson,x) : findpos(lson,x);
}
ll query(int k,ll &x){
if(tree[k].l == tree[k].r){
int res = tree[k].mina - x <= K ? tree[k].r : 0;
checkMin(x,tree[k].minb); return res;
}
pushdown(k);
if(tree[lson].minb < x){
if(tree[k].ans <= K) return query(rson,x = tree[lson].minb);
int res = query(lson,x); checkMin(x,tree[k].minb); return res;
}else{
ll res = tree[lson].mina <= K + x ? findpos(lson,K + x) : 0;
checkMax(res,query(rson,x)); return res;
}
}
} T;
void dfs(int u){
st[++top] = u;
if(nxt[u] <= n) T.modify(1,nxt[u] - 1,n,pre[u] - pre[nxt[u]]);
if(u <= n){
int l = 2, r = top - 1, res = 1;
while(l <= r){
int mid = (l + r) >> 1;
if(pre[st[mid]] - pre[u] > K) res = mid, l = mid + 1;
else r = mid - 1;
}
int maxr = st[res] - 1; ll mn = llINF;
if(u > 1) T.modify(1,1,u - 1,llINF);
T.modify(1,maxr,n,-llINF);
ll pos = T.query(1,mn);
if(u > 1) T.modify(1,1,u - 1,-llINF);
T.modify(1,maxr,n,llINF); checkMax(ans,pos - u + 1);
}
for(int i = head[u];i != -1;i = e[i].next) dfs(e[i].to);
if(nxt[u] <= n) T.modify(1,nxt[u] - 1,n,pre[nxt[u]] - pre[u]);
top -= 1;
}
int main(){
memset(head,-1,sizeof(head));
scanf("%d%d",&n,&K);
for(int i = 1;i < n;i++) scanf("%d",&w[i]);
for(int i = 1;i <= n;i++) scanf("%d",&a[i]);
for(int i = 2;i <= n;i++){
pre[i] = pre[i - 1] + w[i - 1] - a[i - 1];
suf[i] = suf[i - 1] + w[i - 1] - a[i];
}
nxt[n + 1] = n + 1; pre[n + 1] = llINF; ans = 1;
for(int i = n;i >= 1;i--){
while(top && pre[i] >= pre[st[top]]) top -= 1;
nxt[i] = top ? st[top] : n + 1;
st[++top] = i; add_edge(nxt[i],i);
}
top = 0; T.build(1,1,n); dfs(n + 1);
printf("%lld\n",ans);
return 0;
}
CF 504E
Solution
由于要求\(lcp\) ,不难想到二分后通过\(hash\)判断,对于树上的路径,考虑分别维护从上到下和从下到上的\(hash\)值,将\(u\)到\(v\)的路径在\(lca\)处断开即可\(\mathcal O(1)\)求出路径的\(hash\)值,最后在二分还需要快速求出一个点的\(k\)级祖先,长链剖分即可。
时间复杂度\(\mathcal O(n\log n)\)
Code
int n,Q,len,tot;
int head[MAXN],top[MAXN],dep[MAXN],son[MAXN],f[MAXN][LOG],lg[MAXN << 1],maxdep[MAXN];
int pow1[MAXN],pow2[MAXN],inv1[MAXN],inv2[MAXN],dfn[MAXN],st[MAXN << 1][LOG];
char s[MAXN];
vector<int> up[MAXN],down[MAXN];
struct Edge{
int to,next;
} e[MAXN << 1];
struct HashInt{
int val1,val2,len;
HashInt() {}
HashInt(int _val1,int _val2,int _len) : val1(_val1), val2(_val2), len(_len) {}
HashInt operator + (const HashInt &rhs) const {return HashInt(((ll)val1 * pow1[rhs.len] + rhs.val1) % MOD1, ((ll)val2 * pow2[rhs.len] + rhs.val2) % MOD2, len + rhs.len);}
bool operator == (const HashInt &rhs) const {return val1 == rhs.val1 && val2 == rhs.val2 && len == rhs.len;}
void push_back(int x) {val1 = (val1 + (ll)x * pow1[len]) % MOD1; val2 = (val2 + (ll)x * pow2[len]) % MOD2; len += 1;}
void push_front(int x) {val1 = ((ll)val1 * BASE1 + x) % MOD1; val2 = ((ll)val2 * BASE2 + x) % MOD2; len += 1;}
} a[MAXN],b[MAXN]; // a : down to up b : up to down
void add_edge(int u,int v){
e[++len] = (Edge){v,head[u]};
head[u] = len;
}
void dfs1(int u,int fa){
maxdep[u] = dep[u] = dep[fa] + 1; f[u][0] = fa; st[dfn[u] = ++tot][0] = u;
for(int i = 1;i < LOG;i++) f[u][i] = f[f[u][i - 1]][i - 1];
a[u] = a[fa]; a[u].push_front(s[u]); b[u] = b[fa]; b[u].push_back(s[u]);
for(int i = head[u];i != -1;i = e[i].next){
int v = e[i].to;
if(v == fa) continue;
dfs1(v,u); st[++tot][0] = u;
if(maxdep[v] > maxdep[u]) son[u] = v, maxdep[u] = maxdep[v];
}
}
void dfs2(int u,int t){
top[u] = t;
if(u == t){
for(int i = 0, v = u;i <= maxdep[u] - dep[u];i++)
up[u].push_back(v), v = f[v][0];
for(int i = 0, v = u;i <= maxdep[u] - dep[u];i++)
down[u].push_back(v), v = son[v];
}
if(son[u]) dfs2(son[u],t);
for(int i = head[u];i != -1;i = e[i].next){
int v = e[i].to;
if(v != son[u] && v != f[u][0]) dfs2(v,v);
}
}
int query(int u,int k){
if(k >= dep[u]) return 0;
if(!k) return u;
u = f[u][lg[k]]; k -= 1 << lg[k]; k -= dep[u] - dep[top[u]];
u = top[u]; return k >= 0 ? up[u][k] : down[u][-k];
}
int getLCA(int u,int v){
int l = dfn[u], r = dfn[v];
if(l > r) swap(l,r);
int k = lg[r - l + 1];
return min(st[l][k],st[r - (1 << k) + 1][k],[&](const int &x, const int &y) {return dep[x] < dep[y];});
}
HashInt getUp(int u,int v){
int val1 = (ll)(b[u].val1 - b[v].val1 + MOD1) * inv1[b[v].len] % MOD1;
int val2 = (ll)(b[u].val2 - b[v].val2 + MOD2) * inv2[b[v].len] % MOD2;
return HashInt(val1,val2,b[u].len - b[v].len);
}
HashInt getDown(int u,int v){
int val1 = sub(a[v].val1,(ll)a[u].val1 * pow1[a[v].len - a[u].len] % MOD1,MOD1);
int val2 = sub(a[v].val2,(ll)a[u].val2 * pow2[a[v].len - a[u].len] % MOD2,MOD2);
return HashInt(val1,val2,a[v].len - a[u].len);
}
void Init(){
pow1[0] = pow2[0] = 1; inv1[0] = inv2[0] = 1;
int iBASE1 = power(BASE1,MOD1 - 2,MOD1);
int iBASE2 = power(BASE2,MOD2 - 2,MOD2);
for(int i = 1;i <= n;i++){
pow1[i] = (ll)pow1[i - 1] * BASE1 % MOD1;
pow2[i] = (ll)pow2[i - 1] * BASE2 % MOD2;
inv1[i] = (ll)inv1[i - 1] * iBASE1 % MOD1;
inv2[i] = (ll)inv2[i - 1] * iBASE2 % MOD2;
}
dfs1(1,0); dfs2(1,1);
for(int i = 2;i <= tot;i++) lg[i] = lg[i >> 1] + 1;
for(int j = 1;(1 << j) <= tot;j++){
for(int i = 1;i + (1 << j) - 1 <= tot;i++)
st[i][j] = min(st[i][j - 1],st[i + (1 << (j - 1))][j - 1],[&](const int &x, const int &y) {return dep[x] < dep[y];});
}
}
memset(head,-1,sizeof(head));
scanf("%d%s",&n,s + 1);
for(int i = 1,u,v;i < n;i++){
scanf("%d%d",&u,&v);
add_edge(u,v); add_edge(v,u);
}
Init(); scanf("%d",&Q);
while(Q--){
int u1,v1,u2,v2; scanf("%d%d%d%d",&u1,&v1,&u2,&v2);
int lca1 = getLCA(u1,v1), lca2 = getLCA(u2,v2);
int len1 = dep[u1] + dep[v1] - 2 * dep[lca1] + 1;
int len2 = dep[u2] + dep[v2] - 2 * dep[lca2] + 1;
int l = 1, r = min(len1,len2), res = 0;
HashInt t1 = getUp(u1,lca1), t2 = getUp(u2,lca2);
while(l <= r){
int mid = (l + r) >> 1;
HashInt x = dep[u1] - mid + 1 >= dep[lca1] ? getUp(u1,query(u1,mid)) : t1 + getDown(f[lca1][0],query(v1,len1 - mid));
HashInt y = dep[u2] - mid + 1 >= dep[lca2] ? getUp(u2,query(u2,mid)) : t2 + getDown(f[lca2][0],query(v2,len2 - mid));
if(x == y) res = mid, l = mid + 1;
else r = mid - 1;
}
printf("%d\n",res);
}
return 0;
}
CF 575I
Solution
由于四种方向是对称的,我们只考虑\(dir=1\)的情况。
点\((x,y)\)在三角形\((x_0,y_0,len)\)内部或边界的充要条件为\(x\geq x_0\),\(y\geq y_0\)且\(x+y\leq x_0+y_0+len\)直接做是一个四维偏序(还有一维时间),无法通过。考虑对\(x\geq x_0\)和\(y\geq y_0\)容斥,那么除了\(x<x_0,y<y_0,x+y\leq x_0+y_0+len\)这种情况外都是三维偏序。对于这种特殊的情况,不难发现若\(x<x_0,y<y_0\),由于\(len>0\)的,所以\(x+y\)一定是小于\(x_0+y_0+len\)的。于是这还是一个三维偏序为题。最后只需要处理几个三维偏序即可。
由于\(n\leq 5000\),使用二维树状数组维护即可。
时间复杂度\(\mathcal O(q\log^2n)\)
Code
int n,Q;
int x[MAXM],y[MAXM],len[MAXM],dir[MAXM],ans[MAXM];
struct BIT{
int n, m;
int c[MAXN][MAXN];
void clear() {memset(c,0,sizeof(c));}
void resize(int x,int y) {n = x; m = y;}
void modify(int x,int y,int v){
for(int i = x;i <= n;i += i & -i)
for(int j = y;j <= m;j += j & -j)
c[i][j] += v;
}
int query(int x,int y){
int res = 0;
for(int i = x;i >= 1;i -= i & -i)
for(int j = y;j >= 1;j -= j & -j)
res += c[i][j];
return res;
}
} bit;
int main(){
scanf("%d%d",&n,&Q);
for(int i = 1,op;i <= Q;i++){
scanf("%d",&op);
if(op == 1) scanf("%d%d%d%d",&dir[i],&x[i],&y[i],&len[i]);
else scanf("%d%d",&x[i],&y[i]), dir[i] = 5;
}
bit.resize(n,n);
for(int i = 1;i <= Q;i++){
if(dir[i] == 1) bit.modify(x[i],y[i],1), bit.modify(x[i],y[i] + len[i] + 1,-1);
else if(dir[i] == 2) bit.modify(x[i],y[i] - len[i],1), bit.modify(x[i],y[i] + 1,-1);
else if(dir[i] == 3) bit.modify(x[i] + 1,y[i] + len[i] + 1,1), bit.modify(x[i] + 1,y[i],-1);
else if(dir[i] == 4) bit.modify(x[i] + 1,y[i] + 1,1), bit.modify(x[i] + 1,y[i] - len[i],-1);
else if(dir[i] == 5) ans[i] += bit.query(x[i],y[i]);
}
bit.clear(); bit.resize(n + n,n);
for(int i = 1;i <= Q;i++){
if(dir[i] == 2){
bit.modify(x[i] - y[i] + len[i] + n + 2,y[i] + 1,1);
bit.modify(x[i] - y[i] + len[i] + n + 2,y[i] - len[i],-1);
}else if(dir[i] == 3){
bit.modify(x[i] - y[i] - len[i] + n + 1,y[i],1);
bit.modify(x[i] - y[i] - len[i] + n + 1,y[i] + len[i] + 1,-1);
}else if(dir[i] == 5)
ans[i] += bit.query(x[i] - y[i] + n + 1,y[i]);
}
bit.clear(); bit.resize(n + n,n);
for(int i = 1;i <= Q;i++){
if(dir[i] == 1){
bit.modify(x[i] + y[i] + len[i] + 1,n - y[i] + 2,1);
bit.modify(x[i] + y[i] + len[i] + 1,n - y[i] - len[i] + 1,-1);
}else if(dir[i] == 4){
bit.modify(x[i] + y[i] - len[i],n - y[i] + 1,1);
bit.modify(x[i] + y[i] - len[i],n - y[i] + len[i] + 2,-1);
}else if(dir[i] == 5)
ans[i] += bit.query(x[i] + y[i],n - y[i] + 1);
}
for(int i = 1;i <= Q;i++)
if(dir[i] == 5) printf("%d\n",ans[i]);
return 0;
}
CF 643F
Solution
考虑第\(m\)天的答案。
由于需要确保一只熊没有睡觉,因此有效的床的张数可以看成
\(p'=min\{p,n-1\}\)对于每只熊,它有一个喝过的桶的集合。这可以表示成一个\(0/1\)矩阵。我们将矩阵转置,即考虑对于每一桶饮料,喝过它的熊的集合。即考虑给每一个桶维护一个\(m\)行\(n\)列的矩阵,其中第\(i\)行\(j\)列表示第\(i\)天第\(j\)头熊是否喝过这桶酒。
首先,如果有一桶饮料,喝过它的熊的数量超过\(p'\),那么万一这桶饮料是酒,则喝醉的熊的个数就会超过\(p\)′,则要么出现无床可睡的熊,要么所有熊都喝醉,从而挑战无法成功。所以每个矩阵中\(1\)的个数不能超过\(p'\)。
其次,如果有两桶饮料,喝过它的熊的集合相同,且对应熊喝的天数也相同,则如果恰好这些熊喝醉了,剩下的熊就无论怎样也无法知道到底哪一桶是酒,哪一桶是饮料,因此这是不合法的。所以矩阵两两不同。
还有一个限制,那就是矩阵中每一列最多只有一个\(1\),因为万一这桶饮料是酒,那么喝过它的熊就去睡觉了,不可能再喝一次。
所以原问题就等价于计算不同的满足以上限制的矩阵的个数。
考虑枚举矩阵中有\(k\)个\(1\),由于每列最多一个\(1\),所以相当于从\(n\)列中选取\(k\)列,方案数为\(\binom{n}{k}\),然后在有\(1\)的列中,每个\(1\)有\(m\)行可供选择,所以总的方案数为
然后直接\(\mathcal O(pq)\)暴力计算答案即可。
主要组合数的计算方法,考虑将\(\dbinom{n}{k}\)写成\(\dfrac{n^{\underline{k}}}{k!}\),然后暴力枚举上下的每一项,约分即可。
时间复杂度为\(\mathcal O(pq + p^2 \log p)\)
Code
int n,q,p;
ui C[MAXN];
int a[MAXN];
int main(){
scanf("%d%d%d",&n,&p,&q); checkMin(p,n - 1);
C[0] = 1;
for(int i = 1;i <= p;i++){
a[i] = n - i + 1; C[i] = 1;
for(int j = 1, t = i;j <= i && t > 1;j++){
int d = __gcd(t,a[j]);
t /= d; a[j] /= d;
}
for(int j = 1;j <= i;j++) C[i] *= (ui)a[j];
}
ui ans = 0;
for(int i = 1;i <= q;i++){
ui res = 0, t = 1;
for(int j = 0;j <= p;j += 1, t *= i)
res += C[j] * t;
ans ^= res * i;
}
printf("%u\n",ans);
return 0;
}
CF 587D
Solution
首先要求最大值最小,不难想到二分答案。然后考虑如何判断是否合法,由于每条边只有选或不选两种状态,所以考虑\(\mathrm{2-SAT}\),设第\(i\)条边选对应的状态为\(x_i\),不选对应的状态为\(x_i'\)。考虑如何连边:
- 对于一定不能选的边,连边\(x_i\to x_i'\)
- 由于选出的边一定要是一个匹配,考虑一个点\(u\)上的所有边\(x_{1... k}\),连边\(x_i\to x_j'(i\neq j)\)
- 由于剩下的边也一定要是一个匹配,考虑一个点\(u\)上颜色相同的所有边\(x_{1...k}\),连边\(x_i'\to x_j(i\neq j)\)
但是这样连边的边数是\(\mathcal O(n^2)\)的,用前后缀优化即可。
时间复杂度\(O\mathcal ((n+m)\log w)\)
Code
int n,m,top,tot,scc;
int c[MAXN],t[MAXN],stk[MAXN],dfn[MAXN],low[MAXN],in[MAXN],bel[MAXN];
vector<int> e[MAXN],G[MAXN];
int opp(int u) {return u + 3 * m;}
void add_edge(int u,int v){
G[u].push_back(v);
}
void tarjan(int u){
dfn[u] = low[u] = ++tot;
stk[++top] = u; in[u] = 1;
for(int v : G[u]){
if(!dfn[v]) tarjan(v), checkMin(low[u],low[v]);
else if(in[v]) checkMin(low[u],dfn[v]);
}
if(low[u] == dfn[u]){
scc += 1;
do{
bel[stk[top]] = scc;
in[stk[top]] = 0; top -= 1;
}while(stk[top + 1] != u);
}
}
bool check(int x){
tot = top = scc = 0; int cnt = m;
for(int i = 1;i <= m * 6;i++) dfn[i] = low[i] = in[i] = 0, G[i].clear();
for(int i = 1;i <= n;i++){
int col = 0, id = 0;
for(int j : e[i]){
if(t[j] > x) add_edge(j,opp(j));
if(c[j] == col) add_edge(opp(j),id), add_edge(opp(id),j);
cnt += 1;
if(id != 0){
add_edge(cnt - 1,cnt); add_edge(opp(cnt),opp(cnt - 1));
add_edge(cnt - 1,opp(j)); add_edge(j,opp(cnt - 1));
}
add_edge(j,cnt); add_edge(opp(cnt),opp(j));
col = c[j]; id = j;
}
}
for(int i = 1;i <= 6 * m;i++)
if(!dfn[i]) tarjan(i);
for(int i = 1;i <= 3 * m;i++)
if(bel[i] == bel[opp(i)])
return false;
return true;
}
int main(){
scanf("%d%d",&n,&m);
for(int i = 1,u,v;i <= m;i++){
scanf("%d%d%d%d",&u,&v,&c[i],&t[i]);
e[u].push_back(i); e[v].push_back(i);
}
for(int i = 1;i <= n;i++) sort(e[i].begin(),e[i].end(),[&](const int &x,const int &y) {return c[x] < c[y];});
for(int i = 1;i <= n;i++){
int col = 0, cnt = 0;
for(int j : e[i]){
if(c[j] == col){
cnt += 1;
if(cnt > 2) {puts("No"); return 0;}
}else col = c[j], cnt = 1;
}
}
int l = 0,r = 1e9, res = -1;
while(l <= r){
int mid = (l + r) >> 1;
if(check(mid)) res = mid, r = mid - 1;
else l = mid + 1;
}
if(res == -1) {puts("No"); return 0;}
check(res);
printf("Yes\n"); vector<int> ans;
for(int i = 1;i <= m;i++)
if(bel[i] < bel[opp(i)])
ans.push_back(i);
printf("%d %d\n",res,(int)ans.size());
for(int i : ans) printf("%d ",i);
return 0;
}
CF 685C
由于要求是最大值最小,所以考虑二分答案\(dis\)。
那么可以得到\(n\)个形如\(|x-x_i|+|y-y_i|+|z-z_i|\leq dis\)的限制,考虑每个绝对值的正负,可以得到\(2^3=8\)的不等式,合并这\(8n\)个不等式后可以得到一个不等式组
设\(a=-x+y+z\),\(b=x-y+z\),\(c=x+y-z\),则\(x=\frac{b+c}{2}\),\(y=\frac{a+c}{2}\),\(z=\frac{a+b}{2}\),且\(x+y+z = a+b+c\),这相当于\(a,b,c\)奇偶性相同。
令\(a = 2a^{\prime} + r\),\(b = 2b^{\prime} + r\),\(c = 2c^{\prime} + r\),其中\(r \in [0,1]\),则最终得到的不等式组为:
我们要求出这个不等式组的一组解。先贪心的设\(a'=l_2'\),\(b'=l_3'\),\(c'=l_4'\),若不满足\(l_1^{\prime} \leq a^{\prime}+b^{\prime}+c^{\prime} \leq r_1^{\prime}\),则挨个调整\(a',b',c'\)即可。
时间复杂度\(\mathcal O(n\log n)\)
Code
int T,n;
ll ansX,ansY,ansZ;
ll x[MAXN],y[MAXN],z[MAXN];
ll getl(ll x,int i) {return x + ((x & 1) ^ i);}
ll getr(ll x,int i) {return x - ((x & 1) ^ i);}
bool check(ll d){
static ll l[5], r[5];
for(int i = 1;i <= 4;i++) l[i] = -llINF, r[i] = llINF;
for(int i = 1;i <= n;i++){
checkMin(r[1],d + x[i] + y[i] + z[i]);
checkMin(r[2],d - x[i] + y[i] + z[i]);
checkMin(r[3],d + x[i] - y[i] + z[i]);
checkMin(r[4],d + x[i] + y[i] - z[i]);
checkMax(l[1],-d + x[i] + y[i] + z[i]);
checkMax(l[2],-d - x[i] + y[i] + z[i]);
checkMax(l[3],-d + x[i] - y[i] + z[i]);
checkMax(l[4],-d + x[i] + y[i] - z[i]);
}
for(int i = 0;i < 2;i++){
ll l1 = getl(l[1],i), r1 = getr(r[1],i);
ll l2 = getl(l[2],i), r2 = getr(r[2],i);
ll l3 = getl(l[3],i), r3 = getr(r[3],i);
ll l4 = getl(l[4],i), r4 = getr(r[4],i);
if(l1 > r1 || l2 > r2 || l3 > r3 || l4 > r4) continue;
ll a = l2, b = l3, c = l4;
if(a + b + c > r1) continue;
if(a + b + c < l1) a = min(r2,l1 - b - c);
if(a + b + c < l1) b = min(r3,l1 - a - c);
if(a + b + c < l1) c = min(r4,l1 - a - b);
if(a + b + c < l1) continue;
ansX = (b + c) >> 1; ansY = (a + c) >> 1; ansZ = (a + b) >> 1;
return true;
}
return false;
}
void Solve(){
scanf("%d",&n);
for(int i = 1;i <= n;i++) scanf("%lld%lld%lld",&x[i],&y[i],&z[i]);
ll l = 0, r = llINF, res = -1;
while(l <= r){
ll mid = (l + r) >> 1;
if(check(mid)) res = mid, r = mid - 1;
else l = mid + 1;
}
printf("%lld %lld %lld\n",ansX,ansY,ansZ);
}
int main(){
scanf("%d",&T);
while(T--) Solve();
return 0;
}
CF 587F
Solution
首先对所有串建好广义 SAM。设阈值\(B=\sqrt n\)。
对于\(|s_i|>B\)的串,由于这样的串个数不会超过\(\frac{n}{B}\)个,考虑把所有的\(s_i\)离线下来,在 \(\mathrm{parent}\) 树上 \(\mathrm{dfs}\) 一遍求出\(1\)到\(n\)每个串在\(s_k\)中出现次数,做一个前缀和就可以\(\mathcal O(1)\)回答询问了。这一部分的复杂度是\(\mathcal O(\frac{n^2}{B})=\mathcal O(n\sqrt n)\)。由于这题卡空间,当\(B\)取\(\sqrt n\)时无法开下\(\mathcal O(\frac{n^2}{B})\)的\(long \ long\)数组,所以我在实际写代码的时候取\(B=1500\)(好像这样还比\(B\)取\(\sqrt n\)时快了不少)。
对于\(|s_i|\leq B\)的串,考虑将每次询问拆成两段前缀,用扫描线维护,当加入一个点\(i\)时,在 \(\mathrm{parent}\) 树上把\(s_i\)对应的点的子树的权值全部\(+1\),查询的时候枚举\(s_i\)的每一个前缀,询问这个前缀有多少个祖先被扫到,相当于询问这个点对应的权值。用 $\mathrm{dfs} $ 序就可以变成一个区间加单点查询的问题,因为会进行\(\mathcal O(n)\)次修改和\(\mathcal O(n\sqrt n)\)次查询,使用修改\(\mathcal O(n\sqrt n)\)查询\(\mathcal O(1)\)的分块即可。(其实树状数组就能过)
时间复杂度 \(\mathcal O(n\sqrt n)\)
Code
const int INF = 0x3f3f3f3f;
const ll llINF = 1e18;
const int MAXN = 2e5 + 5;
const int MAXM = 2e5 + 5;
const int B = 1500;
const int MAXB = 1e5 / B + 5;
int n,Q,num,totq;
int len[MAXN],prelen[MAXN],id[MAXN],dfn[MAXN],size[MAXN],pos[MAXN],cnt[MAXN][MAXB];
ll ans[MAXN],sum[MAXB][MAXN];
char s[MAXN];
struct SAM{
int tot,last,tim;
int len[MAXN],fa[MAXN],son[MAXN][26];
vector<int> G[MAXN];
SAM() {last = tot = 1;}
void extend(int c){
static int p,q,np,nq;
p = last;
if(son[p][c]){
q = son[p][c];
if(len[q] == len[p] + 1) {last = q; return;}
nq = ++tot; last = tot; len[nq] = len[p] + 1;
memcpy(son[nq],son[q],sizeof(son[nq]));
fa[nq] = fa[q]; fa[q] = nq;
for(;p && son[p][c] == q;p = fa[p]) son[p][c] = nq;
return;
}
np = ++tot; last = tot; len[np] = len[p] + 1;
for(;p && !son[p][c];p = fa[p]) son[p][c] = np;
if(!p) {fa[np] = 1; return;}
q = son[p][c];
if(len[q] == len[p] + 1) {fa[np] = q; return;}
nq = ++tot; memcpy(son[nq],son[q],sizeof(son[nq]));
len[nq] = len[p] + 1; fa[nq] = fa[q]; fa[q] = fa[np] = nq;
for(;p && son[p][c] == q;p = fa[p]) son[p][c] = nq;
}
void dfs(int u){
dfn[u] = ++tim; size[u] = 1;
for(int v : G[u]){
dfs(v); size[u] += size[v];
for(int j = 1;j <= num;j++)
cnt[u][j] += cnt[v][j];
}
}
void init(){
for(int i = 2;i <= tot;i++) G[fa[i]].push_back(i); dfs(1);
for(int i = 1;i <= num;i++)
for(int j = 1;j <= n;j++)
sum[i][j] = sum[i][j - 1] + cnt[pos[prelen[j]]][i];
}
} sam;
struct Block{
int n,tot;
int tag[MAXN],a[MAXN],bel[MAXN],L[MAXN],R[MAXN];
void init(){
n = sam.tot;
for(int i = 1;i <= n;i++) bel[i] = (i - 1) / B + 1;
for(int i = 1;i <= n;i += B) L[++tot] = i, R[tot] = min(i + B - 1,n);
}
void modify(int l,int r,int v){
if(bel[l] == bel[r]){
for(int i = l;i <= r;i++) a[i] += v;
return;
}
for(int i = bel[l] + 1;i < bel[r];i++) tag[i] += v;
for(int i = l;i <= R[bel[l]];i++) a[i] += v;
for(int i = L[bel[r]];i <= r;i++) a[i] += v;
}
int query(int x) {return a[x] + tag[bel[x]];}
} block;
struct Query{
int x,k,op,id;
bool operator < (const Query &rhs) {return x < rhs.x;}
} q[MAXN];
int main(){
scanf("%d%d",&n,&Q);
for(int i = 1;i <= n;i++){
scanf("%s",s + prelen[i - 1] + 1);
len[i] = strlen(s + prelen[i - 1] + 1);
prelen[i] = prelen[i - 1] + len[i];
}
for(int i = 1;i <= n;i++){
sam.last = 1; if(len[i] > B) id[i] = ++num;
for(int j = prelen[i - 1] + 1;j <= prelen[i];j++){
sam.extend(s[j] - 'a'); pos[j] = sam.last;
if(len[i] > B) cnt[sam.last][id[i]] += 1;
}
}
sam.init(); block.init();
for(int i = 1,l,r,k;i <= Q;i++){
scanf("%d%d%d",&l,&r,&k);
if(id[k]) ans[i] = sum[id[k]][r] - sum[id[k]][l - 1];
else q[++totq] = (Query){l - 1,k,-1,i}, q[++totq] = (Query){r,k,1,i};
}
sort(q + 1,q + 1 + totq);
for(int i = 1, j = 1;i <= totq;i++){
while(j <= q[i].x) block.modify(dfn[pos[prelen[j]]],dfn[pos[prelen[j]]] + size[pos[prelen[j]]] - 1,1), j += 1;
for(int j = 1;j <= len[q[i].k];j++)
ans[q[i].id] += q[i].op * block.query(dfn[pos[prelen[q[i].k - 1] + j]]);
}
for(int i = 1;i <= Q;i++) printf("%lld\n",ans[i]);
return 0;
}
CF 590E
Solution
考虑建立 AC 自动机来维护所有子串的关系。然后建出一个 DAG ,其中有边\(u\to v\)当且仅当\(v\)是\(u\)的子串。注意在 AC 自动机上不能暴力跳 fail ,直接跳到最近的结尾同时路径压缩即可。建出图后不难发现整张图是一个 DAG ,而我们要求的就是这张图的最小可重链覆盖,这个和P4298 [CTSC2008]祭祀是一样的,相当于求二分图的最大匹配,匈牙利即可。最后考虑如何输出方案,从二分图左边一个不在最大匹配上的点开始 dfs ,右边到左边只走没有匹配到的边,左边到右边只走匹配到的边,然后所有左边 dfs 到的点和右边未被 dfs 到的点就是这张二分图的最大独立集,它的补集就是最小可重链覆盖了。
时间复杂度 \(\mathcal O(\sum |s_i|+n^3)\)
Code
int n,cur;
int match[MAXN],G[MAXN][MAXN],vis[MAXN],from[MAXN],vis1[MAXN],vis2[MAXN];
char s[MAXM];
struct DSU{
int fa[MAXM];
int find(int x) {return x == fa[x] ? x : fa[x] = find(fa[x]);}
} dsu;
struct ACAutomation{
int tot = 1;
int fail[MAXM],end[MAXM],pos[MAXN],son[MAXM][2],fa[MAXM];
void insert(char *s,int id){
int n = strlen(s + 1), u = 1;
for(int i = 1;i <= n;i++){
int ch = s[i] - 'a';
if(!son[u][ch]) son[u][ch] = ++tot, fa[tot] = u;
u = son[u][ch];
}
end[u] = id; pos[id] = u;
}
void build(){
queue<int> q; fail[1] = 1;
for(int i = 0;i < 2;i++)
if(son[1][i]) q.push(son[1][i]), fail[son[1][i]] = 1;
while(!q.empty()){
int u = q.front(); q.pop();
for(int c = 0;c < 2;c++){
int v = son[u][c];
if(v) fail[v] = son[fail[u]][c], q.push(v);
else son[u][c] = son[fail[u]][c];
}
}
}
void buildgraph(){
for(int i = 1;i <= tot;i++)
dsu.fa[i] = end[i] ? i : fail[i];
for(int i = 1;i <= n;i++){
int u = pos[i];
if(end[dsu.find(fail[u])]) G[i][end[dsu.find(fail[u])]] = 1;
for(u = fa[u];u;u = fa[u]){
if(end[u]) {G[i][end[u]] = 1; break;}
if(end[dsu.find(u)]) G[i][end[dsu.find(u)]] = 1;
}
}
}
} ac;
bool find(int u){
for(int i = 1;i <= n;i++){
if(G[u][i] && vis[i] != cur){
vis[i] = cur;
if(!match[i] || find(match[i])){
match[i] = u; from[u] = i;
return true;
}
}
}
return false;
}
void dfs(int u){
if(vis2[u]) return; vis2[u] = 1;
for(int i = 1;i <= n;i++)
if(G[u][i] && !vis1[i])
vis1[i] = 1, dfs(match[i]);
}
int main(){
scanf("%d",&n);
for(int i = 1;i <= n;i++)
scanf("%s",s + 1), ac.insert(s,i);
ac.build(); ac.buildgraph();
for(int k = 1;k <= n;k++)
for(int i = 1;i <= n;i++)
for(int j = 1;j <= n;j++)
G[i][j] |= G[i][k] & G[k][j];
int ans = n;
for(int i = 1;i <= n;i++) cur += 1, ans -= find(i);
printf("%d\n",ans);
for(int i = 1;i <= n;i++) if(!from[i]) dfs(i);
for(int i = 1;i <= n;i++)
if(!vis1[i] && vis2[i])
printf("%d ",i);
return 0;
}
CF 603E
Solution
首先,存在一个边集使得所有点度数为奇数的充要条件是每个连通块都有偶数个点。
必要性,因为一条边提供的度为\(2\),所以一个连通块总度数必然为偶数,显然不可能出现点数为奇数且度数全为奇数的情况。
充分性,考虑对于每个连通块任取一棵生成树,自底往上考虑每个点是否保留到它父亲的边,这样除了根之外每个点的度数都必然为奇数,而由于总度数为奇数,所以根的度数显然也为奇数。
不难发现,如果加入了一条小于答案的边,那么无论如何答案都不会变大,所以可以用一个类似 kruskal 的方法,从小到大加边直到不存在度数为奇数的连通块为止,此时的最后一条边的边权就是答案了,这个过程可以用并查集维护。
而对于每个点都要计算答案,可以考虑整体二分,将权值离散化之后,设\(solve(l,r,x,y)\)表示考虑\([l,r]\)之间的边且答案为\([x,y]\)的情况,首先将\([l,mid]\)之间所有权值小于\(x\)的边加入,然后再将权值在\([x,y]\)之间且编号小于\(mid\)的边加入直到不存在度为奇数的点,此时的边权就是\(mid\)的答案了。
-
如果一直存在度为奇数的点,那么说明编号在\([l,mid]\)中的边答案都为\(-1\),然后递归处理\(solve(mid+1,r,x,y)\)即可。
-
如果找到了\(mid\)的答案,那么考虑递归分治。往右递归递归\(solve(mid+1,r,x,ans[mid])\)时需要将\([l,mid]\)中小于\(x\)的边加入并查集,往左递归\(solve(l,mid-1,ans[mid],y)\)时需要将\([x,ansmid−1]\)中小于\(l\)的边加入并查集。
由于要支持撤销操作,使用按秩合并的可撤销并查集即可。
由于每条边最多被加入\(\mathcal O(\log m)\)次,每次加入的复杂度为\(\mathcal O(\log n)\),所以复杂度为\(\mathcal O(m\log m\log n)\)
Code
int n,m;
int ans[MAXN];
struct Edges{
int u,v,w,id;
} p[MAXN],q[MAXN];
struct DSU{
int num,top;
int fa[MAXN],size[MAXN];
pii stk[MAXN];
void init(int n) {num = n; for(int i = 1;i <= n;i++) fa[i] = i, size[i] = 1;}
int find(int x) {return x == fa[x] ? x : find(fa[x]);}
void merge(int x,int y){
x = find(x); y = find(y);
if(x == y) return;
if(size[x] < size[y]) swap(x,y);
num -= (size[x] & 1) + (size[y] & 1);
fa[y] = x; size[x] += size[y]; num += (size[x] & 1);
stk[++top] = make_pair(x,y);
}
void undo(){
int x = stk[top].first, y = stk[top].second; top -= 1;
num -= (size[x] & 1); size[x] -= size[y];
fa[y] = y; num += (size[x] & 1) + (size[y] & 1);
}
} dsu;
void solve(int l,int r,int x,int y){
if(l > r) return;
int mid = (l + r) >> 1, lst = dsu.top, ansmid = -1;
for(int i = l;i <= mid;i++)
if(q[i].id < x) dsu.merge(q[i].u,q[i].v);
for(int i = x;i <= y;i++){
if(p[i].id <= mid) dsu.merge(p[i].u,p[i].v);
if(dsu.num == 0) {ansmid = i; break;}
}
while(dsu.top > lst) dsu.undo();
if(ansmid == -1){
for(int i = l;i <= mid;i++) ans[i] = -1;
for(int i = l;i <= mid;i++)
if(q[i].id < x) dsu.merge(q[i].u,q[i].v);
solve(mid + 1,r,x,y);
while(dsu.top > lst) dsu.undo(); return;
}
ans[mid] = p[ansmid].w;
for(int i = l;i <= mid;i++)
if(q[i].id < x) dsu.merge(q[i].u,q[i].v);
solve(mid + 1,r,x,ansmid); while(dsu.top > lst) dsu.undo();
for(int i = x;i <= ansmid;i++)
if(p[i].id < l) dsu.merge(p[i].u,p[i].v);
solve(l,mid - 1,ansmid,y); while(dsu.top > lst) dsu.undo();
}
int main(){
scanf("%d%d",&n,&m); dsu.init(n);
for(int i = 1;i <= m;i++){
scanf("%d%d%d",&q[i].u,&q[i].v,&q[i].w);
p[i] = q[i]; p[i].id = i;
}
sort(p + 1,p + 1 + m,[&](const Edges &x,const Edges &y) {return x.w < y.w;});
for(int i = 1;i <= m;i++) q[p[i].id].id = i;
solve(1,m,1,m);
for(int i = 1;i <= m;i++) printf("%d\n",ans[i]);
return 0;
}
CF 708E
Solution
首先格子连通等价于\(k\)天后任意相邻两行剩下的区间有交。
考虑区间 dp ,设\(f(i,l,r)\)表示前\(i\)行连通且第\(i\)行剩下的区间为\([l,r]\)的概率。
先考虑某一行剩下\([l,r]\)的概率,设\(C(i)\)表示\(k\)天中\(i\)天成功的概率,显然有\(C(i)=\binom{k}{i}p^i(1-p)^{k-i}\)。那么某一行剩下\([l,r]\)的概率为\(P(l,r)=C(l-1)C(m-r)\)
转移非常显然
这样 dp 的时间复杂度为\(\mathcal O(nm^2)\),考虑如何优化,设
那么有
进一步观察发现,\(L\)和\(R\)是对称的,那么便有\(L(i,x)=R(i,m-x+1)\),于是我们只要考虑如何计算\(L(i,x)\)即可。
设\(S(i,r)\)表示右端点为\(r\)的所有\(f(i,l,r)\)之和,则
接下来考虑如何计算\(S(i,r)\)
对\(C(j)\)和\(C(j)L(i,j)\)做前缀和即可\(\mathcal O(1)\)转移。
时间复杂度\(\mathcal O(nm)\)
Code
int n,m,a,b,t;
int f[MAXM],p[MAXM],s[MAXN][MAXN],g[MAXN][MAXN],fac[MAXM],ifac[MAXM];
int main(){
scanf("%d%d%d%d%d",&n,&m,&a,&b,&t);
int x = (ll)a * power(b,MOD - 2) % MOD;
fac[0] = ifac[0] = 1;
for(int i = 1;i <= t;i++) fac[i] = (ll)fac[i - 1] * i % MOD;
ifac[t] = power(fac[t],MOD - 2);
for(int i = t - 1;i >= 1;i--) ifac[i] = (ll)ifac[i + 1] * (i + 1) % MOD;
for(int i = 0;i <= min(m,t);i++)
p[i] = (ll)fac[t] * ifac[i] % MOD * ifac[t - i] % MOD * power(sub(1,x),t - i) % MOD * power(x,i) % MOD;
for(int i = 1;i <= m;i++) f[i] = add(f[i - 1],p[i - 1]);
s[0][m] = 1;
for(int i = 1;i <= n;i++){
for(int j = 1;j <= m;j++){
int v = (ll)p[m - j] * sub((ll)sub(s[i - 1][m],s[i - 1][m - j]) * f[j] % MOD,g[i - 1][j]) % MOD;
s[i][j] = add(s[i][j - 1],v);
g[i][j] = add(g[i][j - 1],(ll)p[j - 1] * s[i][j - 1] % MOD);
}
}
printf("%d\n",s[n][m]);
return 0;
}
CF 571D
Solution
不管是大学还是军队,它们的合并关系相当于给定了两棵树,在合并的时候,我们可以新建一个虚点,让原来两个点所在连通块的根连到虚点。
这样如果仅有大学的操作,我们可以每次给某个连通块的根打一个子树加的 tag ,每次询问的即是某个点到连通块根路径的权值和,可以用带权并查集实现。接着考虑军队的操作,可以发现对于一个点我们只关心它最后被清零的时间,然后就可以对大学的操作离线后差分解决,而每个点最后被清零的时间也可以用带权并查集求出。
时间复杂度\(\mathcal O((n+m)\alpha(n))\)。
Code
struct DSU1{
int f[MAXN],maxt[MAXN];
void init(int n) {for(int i = 1;i <= n;i++) f[i] = i;}
int find(int x){
if(x == f[x]) return x;
int fa = find(f[x]);
if(fa != f[x]) checkMax(maxt[x],maxt[f[x]]);
return f[x] = fa;
}
void merge(int x,int y){
x = find(x), y = find(y);
if(x == y) return; f[x] = y;
}
ll query(int x){
if(f[x] == x) return maxt[x];
int fa = find(x);
return max(maxt[x],maxt[fa]);
}
} dsu1;
struct DSU2{
int f[MAXN]; ll sum[MAXN];
void init(int n) {for(int i = 1;i <= n;i++) f[i] = i;}
int find(int x){
if(x == f[x]) return x;
int fa = find(f[x]);
if(fa != f[x]) sum[x] += sum[f[x]];
return f[x] = fa;
}
void merge(int x,int y){
x = find(x), y = find(y);
if(x == y) return; f[x] = y;
}
ll query(int x){
if(f[x] == x) return sum[x];
int fa = find(x);
return sum[x] + sum[fa];
}
} dsu2;
struct Query{
int x,op,id;
};
vector<Query> q[MAXN];
int n,m,tot;
int size[MAXN];
ll ans[MAXN];
pii a[MAXN];
int main(){
scanf("%d%d",&n,&m); dsu1.init(n + m); dsu2.init(n + m);
for(int i = 1,x,y;i <= m;i++){
char s[5]; scanf("%s",s + 1);
if(s[1] == 'U') scanf("%d%d",&x,&y), a[i] = make_pair(x,y);
else if(s[1] == 'M') scanf("%d%d",&x,&y), dsu1.merge(x,n + i), dsu1.merge(y,n + i);
else if(s[1] == 'A') scanf("%d",&x), a[i] = make_pair(x,0);
else if(s[1] == 'Z') scanf("%d",&x), dsu1.maxt[dsu1.find(x)] = i;
else if(s[1] == 'Q'){
scanf("%d",&x); tot += 1;
int t = dsu1.query(x);
if(t) q[t].push_back((Query){x,-1,tot});
q[i].push_back((Query){x,1,tot});
}
}
for(int i = 1;i <= n;i++) size[i] = 1;
for(int i = 1,x,y;i <= m;i++){
x = a[i].first; y = a[i].second;
if(x && y) dsu2.merge(x,n + i), dsu2.merge(y,n + i), size[x] += size[y];
else if(x) dsu2.sum[dsu2.find(x)] += size[x];
for(auto p : q[i]) ans[p.id] += dsu2.query(p.x) * p.op;
}
for(int i = 1;i <= tot;i++) printf("%lld\n",ans[i]);
return 0;
}
CF 553E
Solution
考虑dp,设\(f(u,i)\)表示\(i\)时刻在\(u\)时的最小期望代价,转移如下
边界条件为对于\(i>t,f(u,i)=dis(u)+x\),其中\(dis(u)\)表示在不考虑时间的情况下从\(u\)到\(n\)的最小代价。
不难发现转移时一个减法卷积,将\(p\)翻转后用分治 FFT 转移即可。因为\(f(n,*)\)是已知的,记\(g(i,j)\)表示在\(j\)时刻走走第\(i\)条边以及之后的最小花费,考虑从后往前先计算\(g(*,mid+1...r)\)和\(f(*,mid+1...r)\),然后考虑\(f(*,mid+1...r)\)对\(g(*,l...mid)\)的贡献即可。
时间复杂度 \(\mathcal O(mt\log ^2 t)\)
Code
typedef vector<double> poly;
namespace Poly{
struct Complex{
double x,y;
Complex(double _x = 0,double _y = 0) : x(_x),y(_y) {}
inline friend Complex operator + (const Complex &x,const Complex &y) {return Complex(x.x + y.x,x.y + y.y);}
inline friend Complex operator - (const Complex &x,const Complex &y) {return Complex(x.x - y.x,x.y - y.y);}
inline friend Complex operator * (const Complex &x,const Complex &y) {return Complex(x.x * y.x - x.y * y.y,x.x * y.y + x.y * y.x);}
inline friend Complex operator / (const Complex &x,const int &y) {return Complex(x.x / y,x.y / y);}
};
int rev[MAXN];
Complex w[MAXN];
inline int Init(int n){
int len = 1; while(len < n) len <<= 1;
for(int i = 0;i < len;i++) rev[i] = (rev[i >> 1] >> 1) | ((i & 1) * (len >> 1));
for(int i = 1;i < len;i <<= 1)
for(int j = 0;j < i;j++)
w[i + j] = Complex(cos(PI * j / i),sin(PI * j / i));
return len;
}
inline void FFT(vector<Complex> &a,int flag){
int n = a.size();
for(int i = 0;i < n;i++)
if(rev[i] < i) swap(a[i],a[rev[i]]);
for(int i = 2;i <= n;i <<= 1){
int mid = (i >> 1);
for(int j = 0;j < n;j += i){
for(int k = j;k < j + mid;k++){
Complex x = a[k], y = a[k + mid] * w[k - j + mid];
a[k] = x + y; a[k + mid] = x - y;
}
}
}
if(flag == -1){
reverse(a.begin() + 1,a.begin() + n);
for(int i = 0;i < n;i++) a[i] = a[i] / n;
}
}
poly PolyMul(const poly &A,const poly &B,int need = 0){
int n = A.size(), m = B.size();
if(n < 5 || m < 5){
poly a; a.resize(n + m - 1);
for(int i = 0;i < n;i++)
for(int j = 0;j < m;j++) a[i + j] += A[i] * B[j];
if(need) a.resize(need);
return a;
}
int len = Init(n + m);
vector<Complex> a(len), b(len);
for(int i = 0;i < n;i++) a[i].x = A[i];
for(int i = 0;i < m;i++) b[i].x = B[i];
FFT(a,1); FFT(b,1);
for(int i = 0;i < len;i++) a[i] = a[i] * b[i];
FFT(a,-1); poly C; C.resize(need ? need : n + m - 1);
for(int i = 0;i < (int)C.size();i++) C[i] = a[i].x; return C;
}
}
int n,m,t,x;
int a[MAXM],b[MAXM];
double d[MAXM][MAXM],f[MAXM][MAXN],g[MAXM][MAXN],c[MAXM],p[MAXM][MAXN];
void CDQ(int l,int r){
if(l == t) return;
if(l == r){
for(int i = 1;i < n;i++) f[i][l] = llINF;
for(int i = 1;i <= m;i++)
if(a[i] != n) checkMin(f[a[i]][l],g[i][l] + c[i]);
return;
}
int mid = (l + r) >> 1; CDQ(mid + 1,r);
poly A, B, C; A.resize(r - l); B.resize(r - mid);
for(int i = 1;i <= m;i++){
if(a[i] != n){
for(int j = 0;j < r - l;j++) A[j] = p[i][j + 1];
for(int j = 0;j < r - mid;j++) B[j] = f[b[i]][r - j];
C = Poly::PolyMul(A,B);
for(int j = l;j <= mid;j++) g[i][j] += C[r - j - 1];
}
}
CDQ(l,mid);
}
int main(){
scanf("%d%d%d%d",&n,&m,&t,&x);
for(int i = 1;i <= n;i++)
for(int j = 1;j <= n;j++)
if(i != j) d[i][j] = llINF;
for(int i = 1;i <= m;i++){
scanf("%d%d%lf",&a[i],&b[i],&c[i]); d[a[i]][b[i]] = c[i];
for(int j = 1;j <= t;j++) scanf("%lf",&p[i][j]), p[i][j] /= 1e5;
}
for(int k = 1;k <= n;k++)
for(int i = 1;i <= n;i++)
for(int j = 1;j <= n;j++)
checkMin(d[i][j],d[i][k] + d[k][j]);
for(int i = 0;i < 2 * t;i++) f[n][i] = (i <= t ? 0 : x);
for(int i = 1;i < n;i++)
for(int j = t;j < 2 * t;j++)
f[i][j] = d[i][n] + x;
CDQ(0,2 * t - 1); printf("%.7f\n",f[1][0]);
return 0;
}
CF605E
Solution
设\(E(u)\)表示从\(u\)到\(n\)的期望步数,考虑下一步,如果是走到\(v\)且\(E(v)\geq E(u)\),那么当前这一步的最优策略肯定是停在原地。
所以我们永远不会走到一个比当前期望步数大的点,所以我们可以考虑将所有答案按从小到大的顺序算出,并以此扩展。
设\(u\)的后继分别为\(v_i\)且\(E\)非降,那么
后面的\(\prod_{j=1}^{i-1} (1-p_{u,v_j})\)表示\(v_i\)可以走且所有期望步数小于\(v_i\)的都不能走,而前面除以\(1-\prod_{j=1}^n(1-p_{v_i,j})\)的原因是要去掉\(v_i\)没法走到其他地方的概率,因为\(E_i\)计算的是不考虑自环的概率,最后再加上当前这一步的贡献\(1\)即可。
用类似 \(\mathrm{dijkstra}\) 的方式每次取最小的转移即可。
时间复杂度 \(\mathcal O(n^2)\)
Code
int n;
int vis[MAXN],a[MAXN];
double p[MAXN][MAXN],E[MAXN],prod[MAXN];
int main(){
scanf("%d",&n);
for(int i = 1;i <= n;i++)
for(int j = 1;j <= n;j++)
scanf("%lf",&p[i][j]), p[i][j] /= 100;
if(n == 1) {printf("0\n"); return 0;}
for(int i = 1;i <= n;i++) E[i] = 1, prod[i] = 1 - p[i][n];
vis[n] = 1; a[1] = n;
for(int i = 1;i <= n;i++){
double minval = llINF; int pos = 0;
for(int j = 1;j <= n;j++){
if(!vis[j] && E[j] / (1 - prod[j]) < minval){
pos = j;
minval = E[j] / (1 - prod[j]);
}
}
vis[pos] = 1;
for(int j = 1;j <= n;j++)
E[j] += E[pos] / (1 - prod[pos]) * p[j][pos] * prod[j], prod[j] *= (1 - p[j][pos]);
}
printf("%.8f\n",E[1] / (1 - prod[1]));
return 0;
}
CF 626G
Solution
不难发现在同一个奖池内押彩票的贡献是递减的,如果没有修改,那么有一个非常显然的做法:拿一个堆维护每一个奖池中新增一张彩票对答案的贡献,每次取出期望收益最大的彩票即可。
考虑修改操作,如果将\(l_i\)减小\(1\),那么押在第\(i\)个奖池的彩票数目可能不变,可能减小\(1\)(因为原来放了\(l_i\)张),或者是增加若干张,不难发现最多只会增加\(1\)张。
证明:假设原来押在第\(i\)个奖池上的彩票有\(c\)张,现在若押了\(c+1\)张以上,那么第\(c+2\)张一定替换了押在其他奖池的彩票,设被替换掉的彩票的贡献为\(w\),由于原来放第\(c+1\)张是不优的,所以\(p_i(\dfrac{c+1}{c+1+l_i}-\dfrac{c}{c+l_i})\leq w\),因此有\(p_i(\dfrac{c+2}{c+2+l_i}-\dfrac{c+1}{c+1+l_i})\leq p_i(\dfrac{c+1}{c+1+l_i}-\dfrac{c}{c+l_i})\leq w\),所以替换后答案会变差,故最多只会增加一张彩票。
对于将\(l_i\)加\(1\)的情况同理,于是可以得出每次修改后最多变化\(1\)张彩票,拿线段树维护押哪一张获得的最大贡献和之前押的彩票中贡献最小的一个,每次修改只需要在线段树上更新这两个点即可。
时间复杂度\(\mathcal O(n\log n)\)
Code
int n,t,Q;
int a[MAXN],num[MAXN];
double p[MAXN];
struct SegmentTree{
struct Tree{
int l,r;
int minid,maxid;
double ans,min,max;
} tree[MAXN << 2];
#define lson k << 1
#define rson k << 1 | 1
void update(int k){
tree[k].ans = tree[lson].ans + tree[rson].ans;
if(tree[lson].max > tree[rson].max) tree[k].max = tree[lson].max, tree[k].maxid = tree[lson].maxid;
else tree[k].max = tree[rson].max, tree[k].maxid = tree[rson].maxid;
if(tree[lson].min < tree[rson].min) tree[k].min = tree[lson].min, tree[k].minid = tree[lson].minid;
else tree[k].min = tree[rson].min, tree[k].minid = tree[rson].minid;
}
void calc(int k,int i){
tree[k].ans = min(p[i] * num[i] / (1.0 * (num[i] + a[i])),p[i] / 2.0);
if(num[i] >= a[i]) tree[k].max = 0;
else tree[k].max = p[i] * (num[i] + 1) / (1.0 * (num[i] + a[i] + 1)) - p[i] * num[i] / (1.0 * (num[i] + a[i]));
if(num[i] == 0) tree[k].min = INF;
else if(num[i] > a[i]) tree[k].min = 0;
else tree[k].min = p[i] * num[i] / (1.0 * (num[i] + a[i])) - p[i] * (num[i] - 1) / (1.0 * (num[i] + a[i] - 1));
tree[k].maxid = tree[k].minid = i;
}
void build(int k,int l,int r){
tree[k].l = l; tree[k].r = r;
if(l == r) {calc(k,l); return;}
int mid = (l + r) >> 1;
build(lson,l,mid); build(rson,mid + 1,r);
update(k);
}
void modify(int k,int x){
if(tree[k].l == tree[k].r) {calc(k,x); return;}
int mid = (tree[k].l + tree[k].r) >> 1;
if(x <= mid) modify(lson,x);
else modify(rson,x);
update(k);
}
double queryans() {return tree[1].ans;}
pair<double,int> querymax() {return make_pair(tree[1].max,tree[1].maxid);}
pair<double,int> querymin() {return make_pair(tree[1].min,tree[1].minid);}
} T;
int main(){
scanf("%d%d%d",&n,&t,&Q);
for(int i = 1;i <= n;i++) scanf("%lf",&p[i]);
for(int i = 1;i <= n;i++) scanf("%d",&a[i]);
T.build(1,1,n);
for(int i = 1;i <= t;i++){
int x = T.querymax().second;
num[x] += 1; T.modify(1,x);
}
while(Q--){
int op,x; scanf("%d%d",&op,&x);
a[x] += (op == 1 ? 1 : -1);
T.modify(1,x);
while(T.querymax().first > T.querymin().first){
int x = T.querymax().second, y = T.querymin().second;
num[x] += 1; num[y] -= 1;
T.modify(1,x); T.modify(1,y);
}
printf("%.8f\n",T.queryans());
}
return 0;
}
CF 527E
Solution
首先一个充要条件是每个点的度数为偶数且总边数为偶数。考虑求如何最少增加的边的数量:随便匹配两个奇数度的点,最后判断是否需要再连一条自环即可。
因为上述条件也等价于图中存在一条欧拉回路。最后跑一遍欧拉回路,对于回路上第奇数条边原方向,偶数条边反方向即可,这样保证了每经过一个点,要么它的入度\(+2\),要么出度\(+2\)。
注意使用链式前向星跑欧拉回路时一定要将用过的边删掉,否则会 TLE。
时间复杂度 \(\mathcal O(n)\)
Code
int n,m,cnt,len = 1;
int deg[MAXN],vis[MAXN],head[MAXN];
struct Edge{
int to,next;
} e[MAXN];
void add_edge(int u,int v){
e[++len] = (Edge){v,head[u]};
head[u] = len;
e[++len] = (Edge){u,head[v]};
head[v] = len;
deg[u] += 1; deg[v] += 1;
}
void dfs(int u){
for(int &i = head[u];i;i = e[i].next){
if(vis[i]) continue;
vis[i] = vis[i ^ 1] = 1;
int v = e[i].to; dfs(v);
cnt += 1;
if(cnt & 1) printf("%d %d\n",u,v);
else printf("%d %d\n",v,u);
}
}
int main(){
scanf("%d%d",&n,&m);
for(int i = 1,u,v;i <= m;i++)
scanf("%d%d",&u,&v), add_edge(u,v);
vector<int> vec;
for(int i = 1;i <= n;i++)
if(deg[i] & 1) vec.push_back(i);
for(int i = 0;i < (int)vec.size();i += 2)
add_edge(vec[i],vec[i + 1]), m += 1;
if(m & 1) add_edge(1,1), m += 1;
printf("%d\n",m); dfs(1);
return 0;
}
CF 611H
Solution
为了便于下文叙述,考虑将每一种位数看成一种颜色,不难发现颜色最多只有\(6\)种。
我们把\(1\)号点当成根,那么整棵树可以看做每条边和其儿子的完美匹配,考虑将颜色相同的点放在一起考虑,每一次取出两个颜色判断在这两个颜色之间连一条边是否合法,合法的话就选择这两个颜色的代表点连边。由于这是一个二分图,根据\(Hall\)定理,如果二分图\((X,Y)\)存在完美匹配且\(|X|\leq |Y|\),那么对于\(X\)的每个子集\(S\),它向另一边连接的点数都要\(\geq |S|\),每次用\(Hall\)定理判断新加入的边是否合法即可。
时间复杂度 \(\mathcal O(\text{能过})\)
Code
int n,m;
int cnt[MAXM][MAXM],num[MAXM],id[MAXM],vis[MAXM];
vector<pii> ans;
bool check(){
for(int S = 1;S < (1 << m) - 1;S++){
int cnta = 0, cntb = 0;
for(int i = 1;i <= m;i++)
if(S >> (i - 1) & 1) cnta += num[i];
for(int i = 1;i <= m;i++)
for(int j = i;j <= m;j++)
if((S >> (i - 1) & 1) | (S >> (j - 1) & 1))
cntb += cnt[i][j];
if(cnta > cntb) return false;
}
return true;
}
int main(){
scanf("%d",&n); int t = n; while(t) t /= 10, m += 1;
id[1] = 1; for(int i = 2;i <= m;i++) id[i] = id[i - 1] * 10;
for(int i = 1;i < m;i++) num[i] = id[i + 1] - id[i]; num[m] = n - id[m] + 1;
for(int i = 1;i < n;i++){
char a[MAXM],b[MAXM]; scanf("%s%s",a + 1,b + 1);
int u = strlen(a + 1), v = strlen(b + 1);
cnt[u][v] += 1; if(u != v) cnt[v][u] += 1;
}
if(!check()) {puts("-1"); return 0;}
int c = 0; vis[1] = 1; id[1] += 1; num[1] -= 1;
while(c < n - 1){
for(int i = 1;i <= m;i++){
if(!vis[i]) continue;
for(int j = 1;j <= m;j++){
if(!cnt[i][j] || !num[j]) continue;
cnt[i][j] -= 1; num[j] -= 1;
if(i != j) cnt[j][i] -= 1;
if(check()){
ans.push_back(make_pair(id[i] - 1,id[j]));
id[j] += 1; vis[j] = 1; c += 1;
}else{
cnt[i][j] += 1; num[j] += 1;
if(i != j) cnt[j][i] += 1;
}
}
}
}
for(auto p : ans) printf("%d %d\n",p.first,p.second);
return 0;
}
CF 566E
Solution
先考虑一般情况,即非叶结点个数大于等于\(3\)
不难发现两个非叶节点\(u,v\)有边,当且仅当存在两个集合的交仅且仅含有两个元素\(u,v\),用 \(\mathrm{bitset}\) 优化就可以得到所有非叶节点之间的边了。
然后考虑叶节点,叶节点对应的集合一定是所有包含它的集合里最小的那个,找出一一对应之后,发现去掉这个集合里所有叶节点,就等于它所连的非叶节点的连边集合,那么我们就可以找到它所连的非叶节点了。
剩下的特殊情况讨论一下即可。
时间复杂度 \(\mathcal O(\frac{n^3}{\omega})\)
Code
int n,cnt;
bool notleaf[MAXN];
bitset<MAXN> a[MAXN],adj[MAXN],vis;
int main(){
scanf("%d",&n);
for(int i = 1,x,y;i <= n;i++){
scanf("%d",&x);
for(int j = 1;j <= x;j++)
scanf("%d",&y), a[i][y] = 1;
}
for(int i = 1;i <= n;i++){
for(int j = i + 1;j <= n;j++){
if((a[i] & a[j]).count() == 2){
bitset<MAXN> tmp = a[i] & a[j];
int x = tmp._Find_first(), y = tmp._Find_next(x);
if(!adj[x][y]){
cnt += 1; printf("%d %d\n",x,y); vis[x] = vis[y] = 1;
adj[x][y] = adj[y][x] = adj[x][x] = adj[y][y] = 1;
notleaf[x] = notleaf[y] = 1;
}
}
}
}
if(cnt == 0){
for(int i = 2;i <= n;i++) printf("%d %d\n",1,i);
return 0;
}
if(cnt == 1){
int x = 0, y = 0;
for(int i = 1;i <= n;i++)
if(notleaf[i]) {x = i; break;}
for(int i = x + 1;i <= n;i++)
if(notleaf[i]) {y = i; break;}
for(int i = 1;i <= n;i++){
if((int)a[i].count() != n){
for(int j = 1;j <= n;j++)
if(!notleaf[j]) printf("%d %d\n",j,a[i][j] ? x : y);
break;
}
}
return 0;
}
for(int i = 1;i <= n;i++){
if(notleaf[i]) continue;
int minn = INF, p = 0;
for(int j = 1;j <= n;j++)
if(a[j][i] && (int)a[j].count() < minn)
minn = a[j].count(), p = j;
bitset<MAXN> tmp = a[p] & vis;
for(int j = 1;j <= n;j++){
if(tmp[j] && tmp == adj[j]){
printf("%d %d\n",i,j);
break;
}
}
}
return 0;
}
CF 700E
Solution
对字符串建出 SAM ,用线段树合并求出每个节点的 endpos 集合。
在 parent 树上从根向下 dp,设\(f_i\)表示到节点\(i\)时的最大值。显然如果父节点的子串在子节点的子串中出现了至少两次,则转移时\(f\)加一,否则不变。
考虑如何判断父节点的子串在子节点的子串中是否出现了至少两次,设此时的子节点为\(u\),父节点为\(fa_u\),找到\(u\)对应的 endpos 中的任意一个位置\(pos\),则 \(pos\) 处 \(fa_u\) 的子串一定出现了一次,所以另一次只要在 \([pos-len_u+len_{fa_u},pos-1]\) 中出现就行了,在线段树上查询即可。
时间复杂度 \(\mathcal O(n \log n)\)。
Code
int n;
struct SegmentTree{
static const int MAXM = MAXN * 30;
int tot;
int sum[MAXM],ls[MAXM],rs[MAXM];
void modify(int &k,int l,int r,int x){
if(!k) k = ++tot; sum[k] += 1;
if(l == r) return;
int mid = (l + r) >> 1;
if(x <= mid) modify(ls[k],l,mid,x);
else modify(rs[k],mid + 1,r,x);
}
int query(int k,int l,int r,int ql,int qr){
if(!k || ql > qr) return 0;
if(l >= ql && r <= qr) return sum[k];
int mid = (l + r) >> 1, res = 0;
if(ql <= mid) res += query(ls[k],l,mid,ql,qr);
if(qr > mid) res += query(rs[k],mid + 1,r,ql,qr);
return res;
}
int merge(int x,int y,int l,int r){
if(!x || !y) return x | y;
int k = ++tot;
if(l == r) {sum[x] += sum[y]; return x;}
int mid = (l + r) >> 1;
ls[k] = merge(ls[x],ls[y],l,mid);
rs[k] = merge(rs[x],rs[y],mid + 1,r);
sum[k] = sum[ls[k]] + sum[rs[k]]; return k;
}
} T;
struct SAM{
int last,tot;
int son[MAXN][26],len[MAXN],fa[MAXN],pos[MAXN],rt[MAXN],bac[MAXN],rk[MAXN];
pii dp[MAXN];
SAM() {last = tot = 1;}
void extend(int c,int id){
static int p,q,np,nq;
p = last; last = ++tot; np = tot;
len[np] = len[p] + 1; pos[np] = id;
for(;p && !son[p][c];p = fa[p]) son[p][c] = np;
if(!p) {fa[np] = 1; return;}
q = son[p][c];
if(len[q] == len[p] + 1) {fa[np] = q; return;}
nq = ++tot; memcpy(son[nq],son[q],sizeof(son[nq]));
len[nq] = len[p] + 1; pos[nq] = pos[q];
fa[nq] = fa[q]; fa[q] = fa[np] = nq;
for(;p && son[p][c] == q;p = fa[p]) son[p][c] = nq;
}
void build(char *s){
for(int i = 1;i <= n;i++) extend(s[i] - 'a',i), T.modify(rt[last],1,n,i);
for(int i = 1;i <= tot;i++) bac[len[i]] += 1;
for(int i = 1;i <= n;i++) bac[i] += bac[i - 1];
for(int i = 1;i <= tot;i++) rk[bac[len[i]]--] = i;
for(int i = tot;i >= 2;i--) rt[fa[rk[i]]] = T.merge(rt[fa[rk[i]]],rt[rk[i]],1,n);
}
void solve(){
int ans = 1;
for(int i = 2,u;i <= tot;i++){
u = rk[i];
if(fa[u] == 1) {dp[u] = make_pair(1,u); continue;}
if(T.query(rt[dp[fa[u]].second],1,n,pos[u] - len[u] + len[dp[fa[u]].second],pos[u] - 1))
dp[u] = make_pair(dp[fa[u]].first + 1,u);
else dp[u] = dp[fa[u]];
checkMax(ans,dp[u].first);
}
printf("%d\n",ans);
}
} sam;
int main(){
char s[MAXN]; read(n); read(s + 1);
sam.build(s); sam.solve();
return 0;
}
CF 566C
Solution
假设现在重心为\(u\),考虑向\(v\)移动会不会更优。
设现在重心在\((u,v)\)这条边上,离\(u\)的距离为\(x\),那么权值为
求导之后可得
只需判断一下此时是否有\(v\)满足导数大于\(0\)即可,若有则往\(v\)移动,但这样做的复杂度是\(\mathcal O(n^2)\)的,无法通过。
不难发现我们每次可以以点分治的重心作为\(u\)来进行上面的过程,这样最多只会移动\(\mathcal O(\log)\)次,总时间复杂度为\(\mathcal O(n\log n)\)
Code
int n,rt,totsize,root;
int size[MAXN],maxpart[MAXN],a[MAXN],vis[MAXN];
double sum1,sum2,ans = 1e20;
double f[MAXN];
vector<pii> G[MAXN];
void GetRoot(int u,int fa){
size[u] = 1; maxpart[u] = 0;
for(auto p : G[u]){
int v = p.first;
if(v == fa || vis[v]) continue;
GetRoot(v,u); size[u] += size[v];
checkMax(maxpart[u],size[v]);
}
checkMax(maxpart[u],totsize - maxpart[u]);
if(maxpart[u] < maxpart[rt]) rt = u;
}
void calc(int u,int fa,int x,int dep){
sum1 += (double)a[u] * dep * sqrt(dep);
sum2 += (double)a[u] * sqrt(dep) * 3 / 2;
f[x] += (double)a[u] * sqrt(dep) * 3 / 2;
for(auto p : G[u]){
int v = p.first;
if(v == fa) continue;
calc(v,u,x,dep + p.second);
}
}
void dfs(int u){
if(vis[u]) return;
vis[u] = 1; sum1 = sum2 = 0;
for(auto p : G[u]){
int v = p.first; f[v] = 0;
calc(v,u,v,p.second);
}
if(sum1 < ans) ans = sum1, root = u;
for(auto p : G[u]){
int v = p.first;
if(sum2 - f[v] * 2 < 0){
totsize = size[v]; rt = 0;
GetRoot(v,u); dfs(rt); break;
}
}
}
void add_edge(int u,int v,int w) {G[u].push_back(make_pair(v,w));}
int main(){
scanf("%d",&n);
for(int i = 1;i <= n;i++) scanf("%d",&a[i]);
for(int i = 1,u,v,w;i < n;i++){
scanf("%d%d%d",&u,&v,&w);
add_edge(u,v,w); add_edge(v,u,w);
}
maxpart[0] = INF; totsize = n; GetRoot(1,0); dfs(rt);
printf("%d %.10f\n",root,ans);
return 0;
}
CF 643D
Solution
对每个节点 \(u\) ,考虑将父亲对\(u\)和\(u\)的儿子对它的贡献分开计算,用一个 \(\text{multiset}\) 维护其儿子节点,再记录其儿子节点数量 \(num_u\) 和该节点所有儿子对它的贡献之和 \(sum_u\) ,那么一个节点的总人数为 \(sum_u+(f_u\text{对} u \text{的贡献})\)。
由于每个点人数的最值必然是某个节点的儿子达到的,所以开一个 \(\text{multiset} \ S\) 维护每个节点的儿子节点最值加上自身对儿子的贡献,即为儿子的总人数。
对于 \(1\) 操作,假设 \(u\) 的父亲由 \(f_u\) 变成 \(v\),那么 \(sum\) 改变的只有 \(u,f_u,f_{f_u},v,f_v\) 这些节点,对应的要把其父亲节点中儿子节点最值从 \(S\) 中删掉,修改对应的 \(sum\) 后再把这些点的父亲节点的儿子节点最值插入到 \(S\) 中完成修改即可.
对于 \(2\) 操作,只需要输出 \(sum_u+(f_u\text{对} u \text{的贡献})\) 即可。
对于 \(3\) 操作,从 \(S\) 中选出最大和最小值即可。
时间复杂度 \(\mathcal O(n\log n)\)
Code
int n,Q;
int f[MAXN],num[MAXN];
ll t[MAXN],sum[MAXN];
multiset<ll> s[MAXN], S;
ll calcself(int i) {return t[i] - (t[i] / (num[i] + 2)) * (num[i] + 1);}
ll calcson(int i) {return t[i] / (num[i] + 2);}
void addmax(int i){
if(s[i].empty()) return;
S.insert(*(--s[i].end()) + calcson(i));
}
void addmin(int i){
if(s[i].empty()) return;
S.insert(*s[i].begin() + calcson(i));
}
void delmax(int i){
if(s[i].empty()) return;
S.erase(S.find(*(--s[i].end()) + calcson(i)));
}
void delmin(int i){
if(s[i].empty()) return;
S.erase(S.find(*s[i].begin() + calcson(i)));
}
void solve(int x,int y){
set<int> tmp = {x,f[x],f[f[x]],y,f[y]}, need,fneed;
for(int i : tmp) if(i) need.insert(i); fneed = need;
for(int i : need) if(f[i]) fneed.insert(f[i]);
for(int i : fneed) delmax(i), delmin(i);
for(int i : need) s[f[i]].erase(s[f[i]].find(sum[i]));
for(int i : need){
sum[i] -= calcself(i);
for(int j : need) if(f[j] == i) sum[i] -= calcson(j);
}
num[f[x]] -= 1; f[x] = y; num[y] += 1;
for(int i : need){
sum[i] += calcself(i);
for(int j : need) if(f[j] == i) sum[i] += calcson(j);
}
for(int i : need) s[f[i]].insert(sum[i]);
for(int i : fneed) addmax(i), addmin(i);
}
int main(){
scanf("%d%d",&n,&Q);
for(int i = 1;i <= n;i++) scanf("%lld",&t[i]);
for(int i = 1;i <= n;i++) scanf("%d",&f[i]), num[f[i]] += 1;
for(int i = 1;i <= n;i++) sum[i] += calcself(i), sum[f[i]] += calcson(i);
for(int i = 1;i <= n;i++) s[f[i]].insert(sum[i]);
for(int i = 1;i <= n;i++) addmax(i), addmin(i);
while(Q--){
int op,x,y; scanf("%d",&op);
if(op == 1) scanf("%d%d",&x,&y), solve(x,y);
else if(op == 2) scanf("%d",&x), printf("%lld\n",sum[x] + calcson(f[x]));
else if(op == 3) printf("%lld %lld\n",*S.begin(),*(--S.end()));
}
return 0;
}
ARC103D
Solution
不难发现一个结论:对于\(d={1,2,4,...,2^k}\),可以走到所有\(|x|+|y|\leq 2k−1\)且\(x+y\equiv 1(mod \ 2)\)的点
证明的话考虑二进制拆分\(x\)和\(y\)坐标即可。
那么有解当且仅当所有数的\(x+y\ mod \ 2\)都要相等,如果都是偶数那么\(d\)集合中再加一个\(1\)即可。
求方案数的话,从大到小枚举\(k\),每次把\(|x|,|y|\)中较大的那个的绝对值减去\(2^k\)即可。
Code
int n;
ll x[MAXN],y[MAXN],cnt[2];
vector<ll> d;
int main(){
scanf("%d",&n);
for(int i = 1;i <= n;i++){
scanf("%lld%lld",&x[i],&y[i]);
cnt[(x[i] + y[i]) & 1] += 1;
}
if(cnt[0] && cnt[1]) {puts("-1"); return 0;}
for(int i = 31;i >= 0;i--) d.push_back(1ll << i);
if(cnt[0]) d.push_back(1);
printf("%d\n",(int)d.size());
for(auto i : d) printf("%lld ",i); printf("\n");
for(int i = 1;i <= n;i++){
for(auto p : d){
if(llabs(x[i]) > llabs(y[i])){
putchar(x[i] < 0 ? 'L' : 'R');
if(x[i] < 0) x[i] += p;
else x[i] -= p;
}else{
putchar(y[i] < 0 ? 'D' : 'U');
if(y[i] < 0) y[i] += p;
else y[i] -= p;
}
}
putchar('\n');
}
return 0;
}
ARC 103F
Solution
考虑如果给定了一棵树,如何求\(D_i\),有一个显然的换根 dp
那么不难发现每一条从叶子到根的链上 \(D\) 的值是一个下凸函数。所以我们用 \(D\) 的值最小的点作为根,这样每一条从叶子到根的链上 \(D\) 的值单调递减。然后显然 \(D\) 的值最大的节点一定是叶子,而且由于 \(D_i\) 互不相同,所以在得到了 \(D_u+2size_u-n\) 之后便可以唯一确定它的父亲。
那么我们按 \(D\) 的值从大到小遍历这些点。显然在 \(u\) 之后遍历到的 \(v\) 一定不会是 \(u\) 的儿子。所以在遍历到 \(u\) 时它所有的儿子都已经计算完毕了,那么便能得到 \(size_u\) 的值。于是就能直接得到 \(u\) 的父亲 \(f\),并更新 \(f\) 的 \(size\) 即可。
最后只需要判断根节点的 \(D\) 是否合法即可。
时间复杂度 \(\mathcal O(n \log n)\)
Code
int n;
int siz[MAXN],fa[MAXN],dep[MAXN];
ll d[MAXN]; ll dis;
map<ll,int> mp;
int main(){
scanf("%d",&n);
for(int i = 1;i <= n;i++)
scanf("%lld",&d[i]), mp[d[i]] = i, siz[i] = 1;
sort(d + 1,d + 1 + n);
for(int i = n;i > 1;i--){
int u = mp[d[i]], f = mp[d[i] + siz[u] * 2 - n];
fa[u] = f; siz[f] += siz[u];
if(!f || fa[f]) {printf("-1\n"); return 0;}
}
for(int i = 2;i <= n;i++){
int u = mp[d[i]];
dep[u] = dep[fa[u]] + 1;
dis += dep[u];
}
if(dis != d[1]) {printf("-1\n"); return 0;}
for(int i = 2;i <= n;i++){
int u = mp[d[i]];
printf("%d %d\n",u,fa[u]);
}
return 0;
}
ARC101D
Solution
先去掉\(1\)出口左边的和\(m\)出口右边的,因为这些点只有一种方法到达出口。设剩下每个点到左边最近的出口和右边最近的出口距离分别为\(x,y\),那么我们将每个机器人看做一个点\((x,y)\),那么一个合法的方案就是从\((0,0)\)开始走一条只往右和往上的路径,那么所有在这条折线上方的都会从左边出口出去,下面的都会从右边出口出去。
可以把折线平移,然后就可以用经过的点来表示折线了。然后考虑 dp ,设\(f_i\)表示折线最后经过的点是\(i\)的方案数,那么有
用树状数组优化 dp 即可,时间复杂度\(\mathcal O(n\log n)\)
Code
int n,m,tot,cnt;
int a[MAXN],b[MAXN],dp[MAXN],c[MAXN];
struct Node{
int x,y;
bool operator == (const Node &rhs) const{
return x == rhs.x && y == rhs.y;
}
bool operator < (const Node &rhs) const{
if(x == rhs.x) return y > rhs.y;
else return x < rhs.x;
}
} p[MAXN];
struct BIT{
int SIZE;
int c[MAXN];
void init(int n,int val = 0) {SIZE = n; for(int i = 1;i <= n;i++) c[i] = val;}
void modify(int x,int val) {for(int i = x;i <= SIZE;i += i & -i) addmod(c[i],val);}
int query(int x) {int res = 0; for(int i = x;i;i -= i & -i) addmod(res,c[i]); return res;}
} bit;
int main(){
scanf("%d%d",&n,&m);
for(int i = 1;i <= n;i++) scanf("%d",&a[i]);
for(int i = 1;i <= m;i++) scanf("%d",&b[i]);
for(int i = 1;i <= n;i++){
if(a[i] <= b[1] || a[i] >= b[m]) continue;
int pos = lower_bound(b + 1,b + 1 + m,a[i]) - b;
if(b[pos] == a[i]) continue;
p[++tot] = (Node){a[i] - b[pos - 1],b[pos] - a[i]};
c[++cnt] = b[pos] - a[i];
}
sort(c + 1,c + 1 + cnt); cnt = unique(c + 1,c + 1 + cnt) - c - 1;
for(int i = 1;i <= tot;i++) p[i].y = lower_bound(c + 1,c + 1 + cnt,p[i].y) - c;
sort(p + 1,p + 1 + tot); tot = unique(p + 1,p + 1 + tot) - p - 1;
dp[0] = 1; bit.init(cnt);
for(int i = 1;i <= tot;i++)
dp[i] = bit.query(p[i].y - 1) + 1, bit.modify(p[i].y,dp[i]);
int ans = 0;
for(int i = 0;i <= tot;i++) addmod(ans,dp[i]);
printf("%d\n",ans);
return 0;
}
CF 611G
Solution
对于每个点\(l\)我们求出一个最大的\(r\)使得\([l,r]\)这一边的多边形的面积小于等于另一边的面积(这里的区间指的是循环下的有序区间),那么中间的贡献是一个等差数列。
注意到随着\(l\)递增,\(r\)单调不降。因此我们可以预处理前缀和,然后双指针扫一遍计算答案即可。
时间复杂度 \(\mathcal O(n)\)
Code
int n;
ll a[MAXN],b[MAXN];
struct Vector{
ll x,y;
Vector(ll _x = 0, ll _y = 0) : x(_x), y(_y) {}
ll operator * (const Vector &rhs) const {return x * rhs.y - y * rhs.x;}
Vector operator + (const Vector &rhs) const {return Vector(x + rhs.x,y + rhs.y);}
Vector operator - (const Vector &rhs) const {return Vector(x - rhs.x,y - rhs.y);}
Vector operator % (const int &rhs) const {return Vector(x % rhs,y % rhs);}
} p[MAXN],pre[MAXN];
int main(){
scanf("%d",&n);
for(int i = 1,x,y;i <= n;i++){
scanf("%d%d",&x,&y); p[i] = Vector(x,y);
pre[i] = (pre[i - 1] + p[i]) % MOD;
}
for(int i = 2;i <= n;i++){
b[i] = b[i - 1] + p[i - 1] * p[i];
a[i] = (a[i - 1] + b[i]) % MOD;
}
b[n + 1] = b[n] + p[n] * p[1]; ll tot = llabs(b[n + 1]); ll ans = 0;
for(int l = 1, r = 3;r <= n;r++){
while(l < r - 1 && tot / 2.0 < llabs(b[r] - b[l] + p[r] * p[l])) l += 1;
ans = (ans + b[r] % MOD * (r - l - 1) - (a[r - 2] - a[l - 1]) + p[r] * (pre[r - 2] - pre[l - 1])) % MOD;
ans = (ans + a[l - 1] + pre[l - 1] * p[r] + (b[n + 1] - b[r]) % MOD * (l - 1) % MOD) % MOD;
ans = (ans + MOD) % MOD;
}
ans = (ans + tot % MOD * inv4 % MOD * n % MOD * (n - 3) % MOD) % MOD;
ans = ans * 2 % MOD; printf("%lld\n",ans);
return 0;
}
AGC 034D
Solution
不难想到费用流,但是若直接建图,则边数会达到\(\mathcal O(n^2)\),不可通过。
注意到\(|x_i−x_j|+|y_i−y_j|=max(x_i+y_i−x_j−y_j,x_i−xj-y_i+y_j,-x_i+x_j+y_i−y_j,-x_i+x_j−y_i+y_j)\)
那么我们可以建四个中间点分别代表上面四种情况,每个点向这四个中间点连边,容量为\(1\),这样边数就降到\(\mathcal O(n)\)了,由于是最大费用,所以保证了这样做的正确性。
最后跑个最大费用最大流即可。
Code
int S,T,tot,n;
int p[5];
struct MinCostMaxFlow{
int len;
int head[MAXN],in[MAXN],id[MAXN],pre[MAXN]; ll dis[MAXN];
queue<int> q;
MinCostMaxFlow() {len = 1; memset(head,-1,sizeof(head));}
struct Edge{
int to,next,flow,cost;
} e[MAXN << 1];
void add_edge(int u,int v,int flow,int cost){
e[++len] = (Edge){v,head[u],flow,cost};
head[u] = len;
e[++len] = (Edge){u,head[v],0,-cost};
head[v] = len;
}
bool spfa(){
for(int i = 1;i <= tot;i++) dis[i] = llINF, id[i] = pre[i] = in[i] = 0;
in[S] = 1; q.push(S); dis[S] = 0;
while(!q.empty()){
int u = q.front(); q.pop(); in[u] = 0;
for(int i = head[u];i != -1;i = e[i].next){
int v = e[i].to;
if(e[i].flow && dis[v] > dis[u] + e[i].cost){
dis[v] = dis[u] + e[i].cost;
pre[v] = u; id[v] = i;
if(!in[v]) in[v] = 1, q.push(v);
}
}
}
return dis[T] != llINF;
}
ll EK(){
ll minCost = 0;
while(spfa()){
int mi = INF;
for(int i = T;i != S;i = pre[i]) checkMin(mi,e[id[i]].flow);
for(int i = T;i != S;i = pre[i]) e[id[i]].flow -= mi, e[id[i] ^ 1].flow += mi;
minCost += (ll)mi * dis[T];
}
return minCost;
}
} mcmf;
int main(){
scanf("%d",&n); tot = (n << 1);
for(int i = 1;i <= 4;i++) p[i] = ++tot;
S = ++tot; T = ++tot;
for(int i = 1,x,y,c;i <= n;i++){
scanf("%d%d%d",&x,&y,&c);
mcmf.add_edge(S,i,c,0);
mcmf.add_edge(i,p[1],INF,-x - y);
mcmf.add_edge(i,p[2],INF,-x + y);
mcmf.add_edge(i,p[3],INF,x - y);
mcmf.add_edge(i,p[4],INF,x + y);
}
for(int i = 1,x,y,c;i <= n;i++){
scanf("%d%d%d",&x,&y,&c);
mcmf.add_edge(i + n,T,c,0);
mcmf.add_edge(p[1],i + n,INF,x + y);
mcmf.add_edge(p[2],i + n,INF,x - y);
mcmf.add_edge(p[3],i + n,INF,-x + y);
mcmf.add_edge(p[4],i + n,INF,-x - y);
}
printf("%lld\n",-mcmf.EK());
return 0;
}

浙公网安备 33010602011771号