【日常小测】Hope

题目大意

给定排列的长度$n(n\le10^5)$和常数$k(k\le10^5)$
对于单个排列$A$中的每$i$位:我们连一条从$i$出发,到其右边第一个比它大的位,如果没有就不连
定义$A$的花费为 $\sum$其中每一个连通块的大小$^k$
询问所有长度为$n$的排列的花费和$(\bmod 998244353)$

解题报告

设$f_i$为排列长度为$i$的答案,假设我们已经求出了$f_{i-1}$
我们考虑枚举$i$放在哪里,那么$i$之前的数全部连向$i$,之后的数是递归一个子问题
于是我们可以写出暴力的$DP$:${f_i} = \sum\limits_{j = 1}^i {{j^k}(j – 1)!C_{i – 1}^{j – 1}{f_{i – j}}} $
我们化一化式子可以得到$\frac{f_i}{(i-1)!}=\sum\limits_{j=1}^{i}{j^k \cdot \frac{f_{i-j}}{(i-j)!}}$
这显然是一个卷积的形式,于是可以用$NTT$优化

另外,对于$f_i$来讲,$f_{1 \sim i-1}$都会对它有贡献
那么这有是一个典型的$CDQ$分治的模型,于是我们还需要套上一个$CDQ$分治

在另外的话,我们回顾一下推出暴力$DP$的过程
不难发现我们是以最大值的插入作为突破口
那么这种题目与最大值相关的问题,这应该算是套路之一吧!
例如:HDU 5575

—————————— UPD 2017.3.17 ——————————
Claris说这题在OEIS上能找到$O(n)$的递推式 QwQ
Claris太强啦! _(:з」∠)_

【51NOD 1195】斐波那契数列的循环节

相关链接

题目传送门:http://www.51nod.com/onlineJudge/questionCode.html#!problemId=1195
二次剩余相关:http://staff.ustc.edu.cn/~lvmin05/jssl/Chap3%20%B6%FE%B4%CE%CA%A3%D3%E0.ppt
二次剩余相关:http://pan.baidu.com/s/1geBDodH
神犇题解:http://blog.csdn.net/acdreamers/article/details/10983813

解题报告

我们分三步走:

  1. 将模数质因数分解
  2. 对于每一个$p_i^m$我们计算其循环节$l_i$
  3. 将所有$l_i$取$lcm$

显然第一步和第三步没有难度
我们考虑第二步,这里有一个神奇得结论:
若$G(p)$为Fibonacci数列在模素数$p$意义下的最短循环节
那么$\bmod p^m$的最短循环节为$G(p)\cdot p^{m-1}$

现在问题转化为求$G(p)$,这里又有一个神奇的结论:
若$5$为$p$的二次剩余,则$G(p)$为$p-1$的因子。否则为$2(p+1)$的因子
然后我们就可以通过从小到大枚举因数+$O(\log n)$判断的方法得到答案了

另外的话,这个算法似乎没有比较准确的复杂度。不过我们可以假装他是$O(\sqrt{n} \log n)$的
再另外的话,这份代码会$T$,但我又不想优化了,反正这个东西也不可能考 QwQ

Code

#include<bits/stdc++.h>
#define ll long long
using namespace std;

inline int read() {
	char c=getchar(); int ret=0,f=1;
	while (c<'0'||c>'9') {if(c=='-')f=-1;c=getchar();}
	while (c<='9'&&c>='0') {ret=ret*10+c-'0';c=getchar();}
	return ret*f;
}

class Fibonacci{
	int ans[4],tra[4],MOD;
	public:
		inline bool cal(int t, int mod) {
			ans[1] = tra[1] = tra[2] = tra[3] = 1;
			ans[0] = ans[2] = ans[3] = tra[0] = 0; MOD = mod;
			Pow(tra, tra, t - 2); Mul(ans, ans, tra);
			return ans[1] == 1 && !ans[0];
		}
	private:
		inline void Pow(int *ans, int *a, int t) {
			static int ret[4],cur[4]; 
			ret[0]=ret[3]=1; ret[1]=ret[2]=0;
			memcpy(cur,a,sizeof(cur));
			for (;t;t>>=1,Mul(cur,cur,cur))
				if (t&1) Mul(ret,cur,ret);
			memcpy(ans, ret, sizeof(ret));
		}
		inline void Mul(int *ans, int *a, int *b) {
			static int ret[4];
			ret[0] = ((ll)a[0] * b[0] + (ll)a[1] * b[2]) % MOD;
			ret[1] = ((ll)a[0] * b[1] + (ll)a[1] * b[3]) % MOD;
			ret[2] = ((ll)a[2] * b[0] + (ll)a[3] * b[2]) % MOD;
			ret[3] = ((ll)a[2] * b[1] + (ll)a[3] * b[3]) % MOD;
			memcpy(ans, ret, sizeof(ret));
		}
}fib;

ll GCD(ll a, ll b) {
	return b? GCD(b, a%b): a;
}

int Pow(int w, int t, int mod) {
	int ret = 1;
	for (;t;t>>=1,w=(ll)w*w%mod)
		if (t&1) ret=(ll)ret*w%mod;
	return ret;
}

inline ll G(ll p) {
	if (p == 2) return 3;
	else if (p == 3) return 8;
	else if (p == 5) return 20;
	static ll LIM = 0; stack<int> stk;
	if (Pow(5, (p-1)/2, p) == 1) LIM = p - 1;
	else LIM = p + 1 << 1; 
	for (int i=1;i*i<=LIM;i++) {
		if (LIM % i == 0) {
			if (fib.cal(i+2, p)) return i;
			else stk.push(LIM / i);
		}
	}
	for (int ret;!stk.empty();) {
		ret = stk.top(); stk.pop();
		if (fib.cal(ret+2, p)) return ret;
	}
}

inline ll cal(int a, int b) {
	static ll INF = 4e18, ret;
	for (ret=G(a);b>1;b--) ret = ret * a;
	return ret;
}

inline ll solve(int mod) {
	static const int N = 1e5;
	static int pri[N],cnt[N],tot;
	if (mod == 1) return 1; tot = 0; 
	for (int i=2;i*i<=mod;i++) {
		if (mod % i == 0) {
			pri[++tot] = i; cnt[tot] = 0;
			for (;mod%i==0;mod/=i) ++cnt[tot];
		}
	} if (mod>1) pri[++tot] = mod, cnt[tot]=1;
	ll ret = 1;
	for (int i=1,tmp;i<=tot;i++) {
		tmp = cal(pri[i], cnt[i]);
		ret = ret / GCD(ret, tmp) * tmp;
	} return ret;
}

int main() {
	for (int T=read();T;T--) 
		printf("%lld\n",solve(read()));
	return 0;
}

【BZOJ 4294】[PA2015] Fibonacci

相关链接

题目传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=4294
神犇题解:http://www.cnblogs.com/clrs97/p/4854720.html

解题报告

做这题,我们需要有一个前置结论:

Fibonacci数列在$\bmod 10^n$的时候,循环节为$6 \times 10^n$

至于这个结论怎么证明,我也不知道
不过这个结论可以用这里的知识自己搞出来:传送门

那么知道了这个结论之后
我们就可以从低位到高位DFS了
每一层只会尝试$0 \sim 9$,虽然看起来会$T$
不过复杂度是玄学,所以跑得飞快

【BZOJ 4700】适者

相关链接

题目传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=4700
神犇题解:http://blog.csdn.net/werkeytom_ftd/article/details/54565251

解题报告

设第$i$个机器人打$t_i$次就会$GG$,攻击力是$v_i$
那么推推式子发现$i$比$j$先死更优,当且仅当$t_iv_j < t_jv_i$
于是我们先排个序,贪一贪心

现在考虑如何一开始就$GG$掉的两个,因为删除之后不会影响决策
所以我们相当于在贪心后的删除序列上再删掉两个点
考虑删掉的第一个点对于第二个点的贡献,形如$av_i+b$
这是一个直线的表达式,于是相当于让你维护一个数据结构,支持插入直线、查询单点最值
这个是一个经典的数据结构问题,可以用李超树来解决
总时间复杂度:$O(n \log n)$

另外,这题还有CDQ分治的做法,时间复杂度仍然为$O(n \log n)$
再另外,我居然暂时是$Rank \ 1$!第一次$Rank \ 1$啊! ★,°:.☆( ̄▽ ̄)/$:.°★

Code

#include<bits/stdc++.h>
#define LL long long
using namespace std;

const int N = 300009;
const int INF = 10000;
const double EPS = 1e-6;

int n,ATK; LL sum[N],pre[N],vout,ans;
struct ROB{int t,v;}r[N];
inline bool cmp(const ROB &A, const ROB &B) {return (LL)A.t * B.v < (LL)B.t * A.v;} 

inline int read() {
	char c=getchar(); int ret=0,f=1;
	while (c<'0'||c>'9') {if(c=='-')f=-1;c=getchar();}
	while (c<='9'&&c>='0') {ret=ret*10+c-'0';c=getchar();}
	return ret*f;
}

class Segment_Tree{
	int root,cnt; LL AnsTmp;
	struct Node{int ch[2],a; LL b,pl,pr;double pm;}p[N<<1];
	public:
		inline void insert(int a, LL b) {
			insert(root, 1, INF, a, b);
		}
		inline LL query(int x) {
			AnsTmp = 0;
			query(root, 1, INF, x);
			return AnsTmp;
		}
	private:
		void insert(int &w, int l, int r, int a, LL b) {
			if (!w) w = ++cnt;
			if (!p[w].a) {p[w].a = a, p[w].b = b; return;}
			LL nl = (LL)a*l+b, nr = (LL)a*r+b; 
			double nm=(l+r)*0.5*a+b; int mid=l+r+1>>1;
			if (nl <= p[w].pl && nr <= p[w].pr) return;
			if (nm > p[w].pm) {
				if (nl < p[w].pl || nr < p[w].pr) {
					if (nl < p[w].pl && l < r) insert(p[w].ch[0], l, mid-1, p[w].a, p[w].b);
					if (nr < p[w].pr && l < r) insert(p[w].ch[1], mid, r, p[w].a, p[w].b);
				} p[w].a = a; p[w].b = b; p[w].pl = nl; p[w].pr = nr; p[w].pm = nm;
			} else {
				if (nl > p[w].pl && l < r) insert(p[w].ch[0], l, mid-1, a, b);
				if (nr > p[w].pr && l < r) insert(p[w].ch[1], mid, r, a, b);
			}
 		}
		void query(int w, int l, int r, int pos) {
			if (!w) return;
			if (p[w].a) AnsTmp = max(AnsTmp, (LL)p[w].a * pos + p[w].b);
			if (l < r) {
				int mid = l + r + 1 >> 1;
				if (pos < mid) query(p[w].ch[0], l, mid-1, pos);
				else query(p[w].ch[1], mid, r, pos);
			}
		}
}SGT;

int main() {
	n = read(); ATK = read();
	for (int i=1,a,b;i<=n;i++) {
		a = read(); b = read();
		b = ceil((double)b / ATK) + EPS;
		r[i].v = a; r[i].t = b;
	}
	sort(r+1, r+1+n, cmp);
	for (int i=1;i<=n;i++) pre[i] = pre[i-1] + r[i].t;
	for (int i=n;i;i--) sum[i] = sum[i+1] + r[i].v;
	for (int i=1;i<=n;i++) {
		LL tmp = SGT.query(r[i].v);
		LL nv = sum[i]*r[i].t-r[i].v+pre[i-1]*r[i].v; 
		if (i > 1) vout = max(vout, tmp + nv);
		SGT.insert(-r[i].t, nv);
	}
	for (int i=1;i<=n;i++) ans += (pre[i] - 1) * r[i].v; 
	printf("%lld\n",ans-vout);
	return 0;
}

【日常小测】巡游计划

题目大意

有$n(n \le 52501)$个城市,依次分布在数轴上
若你到达$i$号城市,则你必须停留$a_i$个单位时间
若你当前在$i$号城市,则你只能前往编号$\in (i,i+k]$的城市
若你在城市$i$,打算前往城市$j$,那么你会花费$|p_i-p_j| \cdot b_i$的时间
现在你从$1$号城市出发,最终到达$n$号城市,问花费的最短时间

解题报告

下面内容建立在你已经会了BZOJ 1492的维护凸壳的做法

先不考虑那个绝对值的限制,显然就是一个斜率DP的形式
我们考虑对序列建一个线段树,每一次得到了$i$号城市的答案后
我们在线段树上进行依次区间加的操作,即将$i$号城市对应的点加入线段树对应结点的凸壳内
因为在线段树上只会被拆分成$O(\log n)$个结点、插入凸壳复杂度$O(\log n)$,于是复杂度为$O(n \log^2 n)$

至于如何查询?我们从左到右依次处理,假设我们当前想得到$i$号城市的答案
那我们在$i$在线段树内对应的节点到根的路径上的每一个凸壳我们都询问一次答案
然后取一个最值就可以了,时间复杂度仍然是$O(n \log^2 n)$的

现在我们考虑绝对值的限制
我们可以钦定$p_i$与$p_j$的大小关系
具体来说,我们在得到$i$号城市的答案后,我们只是将他扔到线段树上去,但先不建立凸包
然后我们对那棵线段树进行中序遍历,那么在走到结点i的时候,我们已经知道了需要查询哪些点,凸壳总共有哪些点
于是我们将这些东西扔到一起,然后排序,然后从小到大遍历一次

如果是查询的点,就到当前的凸壳中更新答案
如果是应该加入到凸壳的点,那就加入到凸壳中
因为已经排过序,所以绝对值可以打开

我们再从大到小再来一遍,这样就可以得到这个结点的所有答案了
不难发现一个点只会被插入凸壳$O(\log n)$次,也只会被查询这么多次
所以时间复杂度仍然为$O(n \log^2 n)$

【日常小测】苹果和雪梨

题目大意

给你$n(n \le 3000)$个梨,$n$个苹果。每个梨有个权值$a_i$,每个苹果有个权值$b_i$
现在让你求出一种匹配关系,使得梨与苹果一一配对,使得获益最大。
设$i$号梨对应$f_i$号苹果,定义收益为$\sum\limits_{i=1}^{n}{a_i \cdot b_{f_i}}-\max\{a_i \cdot b_{f_i}\}$

解题报告

这题有一个非常显然的$O(n^4)$的暴力:

$O(n^2)$枚举最大值是哪一对
对于其他梨,贪心选择最大的、可以选的苹果配对

这种暴力优化一下,据说可以优化到$O(n^3)$

但我们仔细想一想,这种XJB枚举最大值很不优雅,会做很多重复的动作
于是我们考虑从小到大枚举最大值是哪一对
考虑我们此时将涉及到的四个点更改匹配关系即可
时间复杂度:$O(n^2 \log n)$

Code

#include<bits/stdc++.h>
#define LL long long
using namespace std;

const int N = 3009;
const int M = N * N;

LL n,tot,vout,ans,mx,a[N],b[N],p[N],q[N]; 
struct Match{int x,y;LL val;}m[M];

inline int read() {
	char c=getchar(); int ret=0,f=1;
	while (c<'0'||c>'9') {if(c=='-')f=-1;c=getchar();}
	while (c<='9'&&c>='0') {ret=ret*10+c-'0';c=getchar();}
	return ret*f;
}

int main() {
	n = read();
	for (int i=1;i<=n;i++) a[i] = read();
	for (int j=1;j<=n;j++) b[j] = read();
	sort(a+1, a+1+n); sort(b+1, b+1+n);
	for (int i=1;i<=n;i++) {
		for (int j=1;j<=n;j++) {
			m[++tot].val = a[i] * b[j];	
			m[tot].x = i; m[tot].y = j;
		}
	}
	static auto cmp = [](const Match &A, const Match &B) {return A.val < B.val || (A.val == B.val && A.x < B.x) || (A.val == B.val && A.x == B.x && A.y < B.y);};
	sort(m+1, m+1+tot, cmp);
	for (int i=1;i<=n;i++) 
		mx = max(mx, a[i] * b[n-i+1]);
	for (int i=n;i;i--) {
		for (int j=n;j;j--) {
			if (!q[j] && a[i] * b[j] <= mx) {
				q[j] = i; p[i] = j;
				ans += a[i] * b[j];
				break;
			}
		}
	}
	vout = ans - mx;
	for (int i=1,x,y,nx,ny;i<=tot;i++) {
		if (m[i].val <= mx) continue;
		x = m[i].x; y = m[i].y; nx = p[x]; ny = q[y];
		p[ny] = nx; q[nx] = ny; p[x] = y; q[y] = x;
		ans += a[x] * b[y] + a[ny] * b[nx];
		ans -= a[x] * b[nx] + a[ny] * b[y];
		vout = max(vout, ans - m[i].val);
	}
	printf("%lld\n",vout);
	return 0;
}

【HDU 5575】Discover Water Tank

相关链接

题目传送门:http://acm.hdu.edu.cn/showproblem.php?pid=5575
神犇题解:http://blog.csdn.net/qq_19592437/article/details/50252111
中文题面:https://ly.men.ci/problem/199

解题报告

这题我们可以用左偏树+并查集什么的
不过感觉分治更好写吧?

考虑$solve(l,r)$表示区间$(l,r]$的最优解
那么找出其中最高的挡板设为$x$,之后分两种情况讨论

  1. 区间水位低于$x$,那么我们分治$solve(l,x-1)+solve(x+1,r)$即可
  2. 区间水位高于$x$,那么我们搞一个前缀和就好

总的时间复杂度:$O(n \log n)$

【日常小测】摩尔庄园

题目大意

给定一棵$n(n \le 10^5)$个节点的以$1$为根的有根树,第$i$个结点上有$c_i$个食物,其父亲为$\lfloor \frac{i}{2} \rfloor$
现在依次给出$m$个拉姆,依次询问前$i$个拉姆都有东西吃的最短移动距离和
依次输出这$m$次询问的答案

解题报告

如果点数很小的话,我们显然可以跑个费用流就可以了!
但这题点这么多怎么办 QwQ

那我们就手动增广!
考虑维护一个$val_i$表示以$i$为根的子树中最近的有食物的点到$i$的距离
于是我们可以花$O(\log n)$的时间暴力在树上爬一爬就可以代替费用流的SPFA
然后考虑修改沿途边的边权,因为树高是$\log n$级别的,于是我们也是暴力修改就好
最后再用更新一下受影响的$val_i$来方便下次增广就可以辣!

以前听说过手动增广的题目,不过一次都还没有写过
这次写一写感觉这货非常好玩啊!

Code

#include<bits/stdc++.h>
#define LL long long
using namespace std;
 
const int N = 100009;
const int INF = 1e9;
 
int  n,m,vout,cnt[N],pos[N];
int sur[N],val[N],cost[N]; 
 
inline int read() {
    char c=getchar(); int ret=0,f=1;
    while (c<'0'||c>'9') {if(c=='-')f=-1;c=getchar();}
    while (c<='9'&&c>='0') {ret=ret*10+c-'0';c=getchar();}
    return ret*f;
}
 
inline int LCA(int u, int v) {
    for (;u!=v;u>>=1) 
        if (u < v) swap(u, v);
    return u;
}
 
inline void update(int w) {
    static int ls, rs, cl, cr;
    if (cnt[w]) sur[w] = w, val[w] = 0;
    else sur[w] = 0, val[w] = INF;
    if ((ls=w<<1) <= n) {
        cl = cost[ls] > 0? -1: 1;
        if(val[ls] + cl < val[w]) {
            val[w] = val[ls] + cl;
            sur[w] = sur[ls];
        }
    }
    if ((rs=ls|1) <= n) {
        int cr = cost[rs] > 0? -1: 1;
        if(val[rs] + cr < val[w]) {
            val[w] = val[rs] + cr;
            sur[w] = sur[rs];
        }
    }
}
 
inline void modify(int w, int p, int delta) {
    while (w != p) {
        cost[w] += delta;
        update(w); w >>= 1;  
    }
}
 
inline int query(int w, int &ans) {
    static int ret, delta; delta = 0;
    for (;w;w>>=1) {
        if (val[w] + delta < ans) {
            ans = val[w] + delta;
            ret = sur[w];
        }
        delta += cost[w] >= 0? 1: -1;
    } return ret;
}
 
int main() {
    n = read(); m = read();
    for (int i=1;i<=n;i++) cnt[i] = read();
    for (int i=1;i<=m;i++) pos[i] = read();
    for (int i=n;i;i--) update(i);
    for (int i=1,u,v,ans=INF;i<=m;++i,ans=INF) {
        cnt[v=query(u=pos[i], ans)]--; 
        printf("%d\n",vout+=ans);
        int lca = LCA(u, v);
        modify(u, lca, 1); modify(v, lca, -1);
        for (;lca;lca>>=1) update(lca);
    }
    return 0;
}

【BZOJ 4725】[POI2017] Reprezentacje ró?nicowe

相关链接

题目传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=4725
神犇题解:http://blog.csdn.net/lych_cys/article/details/53455978

解题报告

这题看上去一脸不可做QwQ
前前后后想了差不多三个小时吧?
突然反应过来:从第63项开始$a(x)$就大于$10^9$了
换一句话来说:之后的每一项,只可能减去前一项才可能小于$10^9$

于是我们把前$63$项之内的拿出来暴力搞一搞
$63$项之后的,我们可以推公式推出来答案是多少

Code

#include<bits/stdc++.h>
#define LL long long
using namespace std;

const int N = 10000;

int n,tot,vis[N],L[N],R[N],que[N];
LL f[N];

inline int read() {
	char c=getchar(); int ret=0,f=1;
	while (c<'0'||c>'9') {if(c=='-')f=-1;c=getchar();}
	while (c<='9'&&c>='0') {ret=ret*10+c-'0';c=getchar();}
	return ret * f;
}

inline void insert(int w) {
	for (int i=w-1;i;i--) {
		if (abs(f[w]-f[i]) >= N) break;
		vis[abs(f[w]-f[i])] = 1;
	}
} 

inline int query() {
	for (int i=1;i;i++)
		if (!vis[i]) return i;
}

inline void Get_Ans(int w, int id) {
	for (int j=2;j;j++) {
		for (int i=1;i<j;i++) {
			if (f[j] - f[i] == w) {
				L[id] = i; R[id] = j;
				return;
			}
		}
	}
}

inline void query(int w) {
	for (int j=2;j;j++) {
		for (int i=1;i<j;i++) {
			if (f[j] - f[i] == w) {
				cout<<j<<' '<<i<<endl;
				return;
			}
		}
	}
}

int main() {
	f[1] = 1; f[2] = 2; vis[1] = 1;
	for (int i=3;i<=120;i++) { 
		if (i&1) f[i] = f[i-1] << 1;
		else f[i] = f[i-1] + query();
		insert(i);
	} 
	for (int j=2;j<=63;j++) {
		for (int i=1;i<j;i++) {
			if (f[j] - f[i] > 1e9) continue;
			que[++tot] = f[j] - f[i];
		}
	} 
	que[++tot] = (1e9) + 10;
	sort(que+1, que+1+tot);
	for (int i=1;i<tot;i++) Get_Ans(que[i], i); 
	for (int q=read(),x,p;q;q--) {
		x = read();
		p = lower_bound(que+1, que+1+tot, x) - que;
		if (que[p] == x) printf("%d %d\n",R[p], L[p]);
		else if (x <= 90) query(x);
		else printf("%lld %lld\n",(x-90-p+62)*2ll+120,(x-90-p+62)*2ll+119);
	}
	return 0;
}

【Codeforces 781D】Axel and Marston in Bitland

相关链接

题目传送门:http://codeforces.com/contest/781/problem/D

解题报告

话说这个题,你看他的路径定义多么奇怪
构造方式就是倍增嘛!

那我们也来倍增吧!
于是搞一个bitset记录哪些点可以到达就可以辣!
时间复杂度:$O(\frac{n^3 \log 10^{18}}{64})$

Code

#include<bits/stdc++.h>
#define LL long long
using namespace std;

const int N = 501;
const int M = N * N;
const LL INF = 1000000000 * 1000000000ll;

bitset<N> f[60][N][2],ans,pre; 
int n,m;

inline int read() {
	char c=getchar(); int ret=0,f=1;
	while (c<'0'||c>'9') {if(c=='-')f=-1;c=getchar();}
	while (c<='9'&&c>='0') {ret=ret*10+c-'0';c=getchar();}
	return ret*f;
}

int main() {
	n = read(); m = read();
	for (int i=1,u,v;i<=m;i++) {
		u = read(); v = read();
		f[0][u][read()][v] = 1; 
	}
	for (int s=0;s<59;s++) {
		for (int i=1;i<=n;i++) {
			for (int t=0;t<=1;t++) {
				for (int j=1;j<=n;j++) {
					if (f[s][i][t][j]) {
						f[s+1][i][t] |= f[s][j][t^1];
					}
				}
			}
		}
	}
	ans[1] = 1; LL vout = 0;
	for (int s=59,t=0;~s;s--) {
		pre = ans; ans.reset();
		for (int i=1;i<=n;i++) 
			if (pre[i]) ans |= f[s][i][t];
		if (ans.count()) {
			vout += 1ll << s;
			t ^= 1; 
		} else ans = pre;
	} 
	printf("%lld\n",vout>INF?-1:vout);
	return 0;
}

【BZOJ 4373】算术天才⑨与等差数列

相关链接

题目传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=4373
神犇题解:http://blog.csdn.net/TA201314/article/details/51177011

解题报告

首先,假如一个区间满足以下三个条件那么该区间一定合法:

$\%k$后相等
每一个数互不相同
区间和等于某一特定值

后两个条件都可以用线段树维护
问题就是第一个条件非常麻烦

先来考虑一种简单的Hash方式:记录区间的平方和
这样的话,我们可以$O(1)$计算基准值,$O(\log n)$判断是否等于基准值

但众所周知,Hash是不优雅的
于是我们可以先做一个差分序列,然后维护差分序列相邻两项的$\gcd$
考虑如果所有数的差都是$k$的整数倍,那么这些数$ \bmod \ k$之后肯定就相等啦!

另外的话,区间GCD配合差分的题,也不是第一次见了
之前NOI还考过一道:魔幻棋盘
以后和GCD相关的题目要想一想差分行不行啊!

【日常小测】题目2

相关链接

题目传送门:http://paste.ubuntu.com/24087843/
数据生成器:http://paste.ubuntu.com/24087850/
std:http://paste.ubuntu.com/24087841/

题目大意

给定$1 \sim n$的排列$a_i$,再给定$m$个询问、每次给出$l_i,r_i$
求$\sum\limits_{i=l_i}^{r_i}{\sum\limits_{j=l_i}^{i-1}{gcd(a_i,a_j)}}$, $n,m \le 2 \cdot 10^4$

解题报告

一看数据范围就给人一种大暴力的感觉
但用$O(1)$的$GCD$做到$O(nk)$并不能通过此题

std的话非常套路啊!
考虑维护一个集合,支持加入、删除、查询集合中的数两两$GCD$的和
如果可以在$O(\log n)$的时间复杂度完成维护的话,显然可以用莫队做到$O(n^{1.5} \log n)$

现在考虑如何维护,如果分解因数的话,会有算重的部分
但我们列出式子,发现我们计算的实际是:$\sum\limits_{d|gcd(a_i,a_j)}{f(d)}$
如果窝萌把$f(d)$令成$\varphi (d)$,那岂不是刚刚好?
于是我们对于删除和加入,枚举因数$i$,然后在第i个位置加入$\varphi (i)$即可
那么总的复杂度均摊下来是$O(n^{1.5} \log n)$的

另外本题还有一个兄弟版本:https://www.51nod.com/onlineJudge/questionCode.html#!problemId=1439
就是要求维护一个集合中两两互质的对数,把$\varphi(i)$换成$\mu(i)$就是一样的做法了
全™是套路啊!

Code

#include<bits/stdc++.h>
#define LL long long
using namespace std;

const int N = 100009;

LL vout[N],cnt[N],ans;
int n,m,tot,BLK,pri[N],arr[N],phi[N];
vector<int> divs[N]; bool vis[N]; 
struct Query{int l,r,id,blk;}q[N];

inline int read() {
	char c=getchar(); int f=1,ret=0;
	while (c<'0'||c>'9') {if(c=='-')f=-1;c=getchar();}
	while (c<='9'&&c>='0') {ret=ret*10+c-'0';c=getchar();}
	return ret * f;
}

inline void Modify(int w, int t) {
	for (int j=divs[w].size()-1,c;~j;j--) {
		c = divs[w][j];
		if (~t) ans += cnt, cnt += phi;
		else cnt -= phi, ans -= cnt;
	}
}

int main() {
	freopen("B.in","r",stdin);
	freopen("B.out","w",stdout);
	n = read(); BLK = sqrt(n) + 1; phi[1] = 1;
	for (int i=2;i<=n;i++) {
		if (!vis[i]) phi[i] = i-1, pri[++tot] = i;
		for (int j=1;j<=tot&&pri[j]*i<=n;j++) {
			vis[i*pri[j]] = 1;
			if (i % pri[j]) phi[i*pri[j]] = phi[i] * phi[pri[j]];
			else {phi[i*pri[j]] = phi[i] * pri[j]; break;}
		}
	} 
	for (int i=1;i<=n;i++) {
		arr[i] = read();
		for (int j=i;j<=n;j+=i)
			divs[j].push_back(i);
	} m = read();
	for (int i=1;i<=m;i++) {
		q[i].l = read(); q[i].r = read();
		q[i].id = i; q[i].blk = q[i].l / BLK;
	}	
	sort(q+1, q+1+m, [](const Query &A, const Query &B){return A.blk<B.blk||(A.blk==B.blk&&A.r<B.r);});
	for (int i=1,l=1,r=0;i<=m;i++) {
		while (r > q[i].r) Modify(arr[r--], -1);
		while (r < q[i].r) Modify(arr[++r], 1);
		while (l > q[i].l) Modify(arr[--l], 1);
		while (l < q[i].l) Modify(arr[l++], -1);
		vout[q[i].id] = ans; 
	}
	for (int i=1;i<=m;i++) printf("%lld\n",vout[i]);
	return 0;
}

【日常小测】题目1

相关链接

题目传送门:http://paste.ubuntu.com/24087405/
数据生成器:http://paste.ubuntu.com/24087409/
std:http://paste.ubuntu.com/24087412/

题目大意

求$\sum_{i=1}^n{f_i^k}$
其中$f_x$为第$x$项$Fibonacci$数
$n \le 1e18, k \le 1e5$

解题报告

之前鏼爷讲过二次剩余,于是看到这个模数就知道方向了
在暴力求出$\sqrt 5$的二次剩余后,我们可以推出答案长这样
$$\sum_{j=0}^{k}{(-1)^{k-j} \cdot C_k^j \sum_{i-1}^n{(A^jB^{k-j})^i}}$$
于是我们搞一搞组合数,逆元什么的这题就做完啦!

Code

#include<bits/stdc++.h>
#define LL long long
using namespace std;

const int MOD = 1000000009;
const int A = 691504013;
const int B = 308495997;
const int REV_SQRT_FIVE = 276601605;
const int N = 100009;

int k,vout,PA[N],PB[N];

inline int read() {
	char c=getchar(); int f=1,ret=0;
	while (c<'0'||c>'9') {if(c=='-')f=-1;c=getchar();}
	while (c<='9'&&c>='0') {ret=ret*10+c-'0';c=getchar();}
	return ret * f;
}

inline int Pow(int w, LL t) {
	int ret = 1;
	for (;t;t>>=1,w=(LL)w*w%MOD) 
		if (t & 1) ret = (LL)ret * w % MOD;
	return ret;
}

inline int Mul(int a, LL b) {
	int ret = 0;
	for (;b;b>>=1,(a<<=1)%=MOD)
		if (b & 1) (ret += a) %= MOD;
	return ret;
}

int main() {
	freopen("A.in","r",stdin); 
	freopen("A.out","w",stdout); 
	LL n; cin>>n; k = read();
	PA[0] = PB[0] = 1;
	for (int i=1;i<=k;i++) {
		PA[i] = (LL)PA[i-1] * A % MOD;
		PB[i] = (LL)PB[i-1] * B % MOD;
	}
	for (int i=0,c=1,v;i<=k;i++) {
		v = (LL)PA[i] * PB[k-i] % MOD; 
		if (v == 1) v = Mul(v, n);
		else v = ((LL)Pow(v, n+1) - v) * Pow(v-1, MOD-2) % MOD;
		if (k-i & 1) (vout -= (LL)c * v % MOD) %= MOD;
		else (vout += (LL)c * v % MOD) %= MOD;
		c = ((LL)c * (k - i) % MOD) * Pow(i+1, MOD-2) % MOD; 
	} 
	printf("%d\n",((LL)vout*Pow(REV_SQRT_FIVE,k)%MOD+MOD)%MOD);
	return 0;
}

【BZOJ 4713】迷失的字符串

相关链接

题目传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=4713

解题报告

这题拿bitset搞一搞就好了
f[i]表示前缀匹配,g[i]表示后缀匹配
然后暴力更新
时间复杂度:$O(\frac{n^2}{64})$

Code

#include<bits/stdc++.h>
#define LL long long
using namespace std;

const int M = 16;
const int SGZ = 26;
const int N = 30000+9;

int head[N],nxt[N<<1],to[N<<1],col[N<<1],sz[N],hvy[N];
int n,m,tot,beg[N],ed[N],id[N],pool[M],top; char s[N];
bitset<N> vout,ans,ve,vis[SGZ],vs[SGZ],f[M],g[M];

inline int read() {
	char c=getchar(); int f=1,ret=0;
	while (c<'0'||c>'9') {if(c=='-')f=-1;c=getchar();}
	while (c<='9'&&c>='0') {ret=ret*10+c-'0';c=getchar();}
	return ret * f;
}

inline void Add_Edge(int u, int v, int c) {
	static int E = 1;
	to[++E] = v; nxt[E] = head[u]; head[u] = E; col[E] = c;
	to[++E] = u; nxt[E] = head[v]; head[v] = E; col[E] = c;
}

void DFS1(int w, int f) {
	sz[w] = 1;
	for (int i=head[w];i;i=nxt[i]) {
		if (to[i] != f) {
			DFS1(to[i], w);
			sz[w] += sz[to[i]];
			if (sz[hvy[w]]<sz[to[i]]) 
				hvy[w] = to[i];
		}
	}
}

inline void init(int w) {
	id[w] = pool[top--];
	f[id[w]].reset();
	g[id[w]] = ve;	
}

inline void solve(int x, int y, int c) {
	int ix = id[x], iy = id[y];
	f[iy] <<= 1; f[iy] &= vis; f[iy] |= vs;
	g[iy] = g[iy] & vis;
	vout |= g[iy];
	g[iy] >>= 1;
	ans |= f[ix] & g[iy];
	ans |= g[ix] & f[iy];
	f[ix] |= f[iy];
	g[ix] |= g[iy];
	pool[++top] = iy;
}

void DFS2(int w, int f) {
	if (hvy[w]) DFS2(hvy[w], w);
	init(w);
	for (int i=head[w];i;i=nxt[i]) if (to[i] == hvy[w]) 
		solve(w, hvy[w], col[i]);
	for (int i=head[w];i;i=nxt[i]) if (to[i] != hvy[w] && to[i] != f) 
		DFS2(to[i], w), solve(w, to[i], col[i]);
} 

int main() {
	for (int i=top=M-1;~i;i--) pool[i] = i;
	for (int i=n=read(),u,v;i>1;i--) {
		u = read(); v = read(); 
		scanf("%s",s);
		Add_Edge(u, v, s[0]-'a');
	}
	for (int i=m=read(),len;i;i--) {
		scanf("%s",s); len = strlen(s); 
		vs[s[0]-'a'][beg[m-i+1]=tot+1] = 1; 
		for (int j=0;j<len;j++) vis[s[j]-'a'][++tot] = 1;
		ve[ed[m-i+1]=tot] = 1; 
	}
	DFS1(1, 1); 
	DFS2(1, 1);
	for (int i=1,tag=0;i<=m;i++,tag=0) {
		if (vout[beg[i]]) tag = 1;
		for (int j=beg[i];j<=ed[i]&&!tag;j++) if (ans[j]) tag = 1;
		puts(tag?"YES":"NO");
	}
	return 0;
}

后记

这题似乎之前听谁说过有点分的做法?
不过想不起怎么做了 _(:з」∠)_
会的神犇可不可以给我讲一讲怎么做啊 QwQ

【BZOJ 4236】JOIOJI

相关链接

题目传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=4236
神犇题解:http://www.cnblogs.com/qscqesze/p/4775481.html

解题报告

如果字符集大小只有2的话,那一个搞成1另一个搞成-1就好啦!
但这题字符集大小为3,于是我们从数组拓展到三维坐标系就好啦!
然后搞一个map什么的装一装,时间复杂度:$O(nlogn)$

Code

#include<bits/stdc++.h>
#define LL long long
using namespace std;

int n,vout;
map<pair<int,int>, int> m;

inline int read() {
	char c=getchar(); 
	while (c!='J'&&c!='O'&&c!='I') c=getchar();
	if (c == 'J') return 1;
	else if (c == 'O') return 2;
	else return 3;
}

int main() {
	cin>>n;
	pair<int,int> cur(0,0);
	for (int i=1,w;i<=n;i++) {
		w = read();
		if (w == 1) cur.first++;
		else if (w == 2) cur.second++;
		else cur.first--, cur.second--;
		if (m[cur]) vout = max(vout, i - m[cur]);
		else m[cur] = i;
		if (!cur.first && !cur.second) vout = max(vout, i);
	}
	cout<<vout<<endl;
	return 0;
}

【BZOJ 4237】稻草人

相关链接

题目传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=4237
神犇题解:http://blog.csdn.net/lqybzx/article/details/51784992

解题报告

这题一年前就考过,当时连$O(n^2)$的容斥都不会 QwQ
当时知道std是分治的时候,眼珠子都快掉出来了
现在来看的话,因为做过BZOJ 4456,所以还是勉强能想出来?

考虑对y轴分治,这样每一次就只需要考虑跨过分治线的所有矩形了
这样的话,我们枚举合法矩形的右上端点,合法的左下端点的纵坐标就是单调下降的了
于是维护一个单调栈什么的就可以啦!