【HDU 4630】No Pain No Game

相关链接

题目传送门:http://acm.hdu.edu.cn/showproblem.php?pid=4630
神犇题解:http://www.cnblogs.com/zhsl/p/3234099.html

吐槽

最开始写了一个复杂度玄学的莫队,然后被卡$T$了
然后又写了一个$O(n \log n \sqrt{n \log n})$的$KD-Tree$,然后又被卡$T$了

然后看了题解就给跪了,这™不是傻逼题吗?

解题报告

我们将询问按照左端点排序,然后降序处理
这样每一次询问就相当于问一个后缀的前缀

然后考虑新加入最左边一个数$a_i$对于答案的影响
显然我们可以枚举因数$v$,然后记$v$上一次出现的位置为$last_v$
那么右端点在$i \sim last_v$之间的询问都会计算到这个$\gcd$
于是我们用$BIT$维护一个前缀最值就可以了
时间复杂度:$O(n \log^2 n)$

Code

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

const int N = 50009;

int n,m,pos[N],arr[N],last[N],ans[N];
struct Query{
	int l,r,id;
	inline bool operator < (const Query &Q) const {
		return l < Q.l;
	}
}q[N];
vector<int> que[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;
}

class Fenwick_Tree{
	#define lowbit(x) ((x)&-(x))
	int mx[N];
	public:
		inline void init() {
			memset(mx, 0, sizeof(mx));
		}
		inline void modify(int p, int v) {
			for (int i=p;i<=n;i+=lowbit(i)) {
				mx[i] = max(mx[i], v);
			}
		}
		inline int query(int p) {
			int ret = 1;
			for (int i=p;i;i-=lowbit(i)) {
				ret = max(ret, mx[i]);
			}
			return ret;
		}
}BIT;

int main() {
	for (int T=read();T;T--) {
		n = read();
		for (int i=1;i<=n;i++) {
			que[i].clear();
			arr[i] = read();
			pos[arr[i]] = i; 
		}
		for (int i=2;i<n;i++) {
			for (int j=i;j<=n;j+=i) {
				que[pos[j]].push_back(i);
			}
		} 
		m = read();
		for (int i=1,l,r;i<=m;i++) {
			q[i].l = read();
			q[i].r = read();
			q[i].id = i;
		}
		sort(q+1, q+1+m);
		BIT.init();
		memset(last,60,sizeof(last));
		for (int i=m,cur=n+1;i;i--) {
			while (cur > q[i].l) {
				--cur;
				for (int j=0;j<que[cur].size();j++) {
					int v = que[cur][j];
					if (last[v] <= n) {
						BIT.modify(last[v], v);
					}
					last[v] = cur;
				}
			}
			if (q[i].l == q[i].r) ans[q[i].id] = 0;
			else ans[q[i].id] = BIT.query(q[i].r);
		}
		for (int i=1;i<=m;i++) {
			printf("%d\n",ans[i]);
		}
	}
	return 0;
}

【BZOJ 4837】LRU算法

相关链接

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

解题报告

维护一个滑动窗口就可以了
时间复杂度是线性的

Code

#include<bits/stdc++.h>
#define LL long long
#define ULL unsigned long long
using namespace std;
  
const int N = 1000009; 
  
int n,m,p,A,B,MOD,cnt[N],x[N],last[N];
ULL ans,pre[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;
}
  
int main() {
    for (int i=1;i<N;i++) pre[i] = pre[i-1] + (ULL)i;
    for (int T=read();ans=0,T;T--) {
        memset(cnt,0,sizeof(cnt));
        memset(last,60,sizeof(last));
        n = read(); m = read(); p = read(); 
        A = read(); B = read(); MOD = read();
        for (int i=1;i<=m;i++,p=((LL)A*p+B)%MOD) x[i] = p;
        for (int i=m,cur=0,tl=m,r;i;i--) {
            if (++cnt[x[i]] == 1) ++cur;
            while (cur>n) if (--cnt[x[tl--]] == 0) cur--;
            ans += (ULL)x[i] * (pre[min(last[x[i]], tl)] - pre[i-1]); 
            last[x[i]] = i - 1;
        }
        printf("%llu\n",ans);
    }
    return 0;
}

【TopCoder SRM712】LR

相关链接

题目传送门:https://community.topcoder.com/stat?c=problem_statement&pm=14569&rd=16881

解题报告

我们经过观察发现,操作顺序不影响结果
于是我们就可以暴力枚举多少个$L$多少个$R$然后暴力判断
时间复杂度:$O(n \log^2 10^{17})$

Code

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

class LR {
    public:
    	string construct(vector<long long> s, vector<long long> t) {
    	    int n=s.size(),SPJ=1; LL s1=0,s2=0,pp,cnt=0;
    	    for (int i=0;i<n;i++) s1 += s[i], s2 += t[i], SPJ = (t[i]!=s[i]?0:SPJ);
    	    if (SPJ) return ""; pp = s1;
    	    if (s2-s1 <= 0 || (!s1 && s2)) return "No solution";
    	    while (pp<s2) pp<<=1,++cnt; 
			if (pp != s2) return "No solution";
			for (int i=0;i<=cnt;i++) {
				if (judge(s, t, i, cnt-i)) {
					string ret="";
					for (int j=1;j<=i;j++) ret += "L";
					for (int j=i+1;j<=cnt;j++) ret += "R";
					return ret;
				}
			}
			return "No solution";
   		}
   	private:
   		inline bool judge(vector<LL> s, vector<LL> t, int L, int R) {
			LL tmp[55],a[55],b[55],n=s.size();
			for (int i=1;i<=n;i++) a[i] = s[i-1], b[i] = t[i-1];
			for (int k=1;k<=L;k++) {
				for (int i=1;i<=n;i++) tmp[i] = a[i]; tmp[0] = a[n];
				for (int i=1;i<=n;i++) a[i] = tmp[i-1] + tmp[i];
			}
			for (int k=1;k<=R;k++) {
				for (int i=1;i<=n;i++) tmp[i] = a[i]; tmp[n+1] = a[1];
				for (int i=1;i<=n;i++) a[i] = tmp[i] + tmp[i+1];
			}
			for (int i=1;i<=n;i++) if (a[i] != b[i]) return 0;
			return 1;
		} 
};

【日常小测】苹果

题目大意

给定一个长度为$2n(n \le 100000)$的序列,序列中每个元素$\in [1,10^9]$
让你从中选出$n$个元素,使这$n$个元素的$\gcd$最大

解题报告

我们随机选两个数的话,失败的概率为$\frac{3}{4}$
于是我们选$30 \sim 50$次的话,错误的概率就足够小了

于是我们就随机选几对数,然后求$\gcd$
之后枚举$\gcd$的因数,去更新答案
时间复杂度:$O($跑得过$)$

Code

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

const int N = 200009;
const int M = 1000009;
const int TIMES = 30;
const double EPS = 0.5;

int n,tp,tot,vis[M],pri[M],cnt[N];
LL arr[N],que[N],ans=1; set<LL> VIS;

inline LL read() {
	char c=getchar(); LL 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;
}

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

bool DFS(int t, LL v) {
	if (t > tot) {
		if (v <= ans || VIS.count(v)) return 1; 
		int ret = 0; VIS.insert(v); 
		for (int i=1;i<=n;i++) ret += (arr[i] % v == 0);
		if (ret >= (n >> 1)) {ans = v; return 1;}
		else return 0;
	} else {
		bool ret = 0; 
		for (int i=0;i<=cnt[t];i++,v*=que[t]) {
			if (!DFS(t+1, v)) break;
			else ret = 1;
		} return ret;
	}
}

int main() {
	freopen("apple.in","r",stdin);
	freopen("apple.out","w",stdout);
	srand(19260817); 
	for (int i=2;i<M;i++) {
		if (!vis[i]) pri[++tp] = i;
		for (int j=1;j<=tp&&pri[j]*i<M;j++) {
			vis[i*pri[j]] = 1;
			if (i % pri[j] == 0) break; 
		}
	} 
	
	n = read(); 
	for (int i=1;i<=n;i++) arr[i] = read();
	for (int i=2;i<=n;i++) swap(arr[i], arr[rand()%(i-1)+1]);
	for (int a,b,kk=1;kk<=TIMES;kk++) {
		a = rand() % n + 1; b = rand() % n + 1;
		LL gcd = GCD(arr[a], arr[b]); tot = 0; 
		for (int i=1,v;i<=tp&&(v=pri[i])<=gcd;i++) {
			if (gcd % v != 0) continue;
			que[++tot] = v; cnt[tot] = 0;
			for (;gcd%v==0;++cnt[tot]) gcd /= v; 
		}
		if (gcd != 1) que[++tot] = gcd, cnt[tot] = 1;
		DFS(1, 1);
	}
	printf("%lld\n",ans);
	return 0;
}

【BZOJ 4810】[YNOI2017] 由乃的玉米田

相关链接

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

解题报告

看一看似乎一脸懵逼啊
询问两数相加、相减为特殊值不是只能$FFT$吗?
你这™还带区间限制,那怎么做啊……

但仔细看看值域范围,发现这货只有$10^{10}$
配上$30s$的限制,刚好是$bitset$的数据范围啊!
于是仔细想一想,加法可以直接减法可以直接左移然后$and$起来
至于减法,我们可以记一下负数就可以转化为减法了
至于相乘嘛,我们可以暴力枚举因数!
于是再配上区间的莫队,时间复杂度:$O(n \sqrt{n} + \frac{mc}{64})$

【CodeChef PARADE】Annual Parade

相关链接

题目传送门:https://www.codechef.com/problems/PARADE
神犇题解:http://blog.csdn.net/jasonvictoryan/article/details/53395098

解题报告

这题先只考虑一个询问的情况

这显然可以使用拆点+二分图最大权匹配去做
具体来说:每个点拆成出度和入度两个点,然后出度放左边,入度放右边
考虑每找到一条增广路,就是在原图中走了一条边
那么不管是连接了两条路径,或是新走到一个点,都会使总费用减少一个$C$
但每走一次增广路,都会花费一些费用$v$
显然我们应该在$v > C$的时候停止增广

现在考虑多个询问
因为是费用流算法,所以单次增广的费用$v_i$是单调不减的
于是我们可以记录每一次增广的$v_i$。对于询问就二分,然后求前缀和就好
当然不想二分,也可以先排个序然后扫一遍,反正总的时间复杂度主要还是卡在费用流那里

【BZOJ 3551】[ONTAK2010] Peaks加强版

相关链接

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

解题报告

这题强制在线,所以不能直接上启发式合并
或者强行可持久化也可以?
不过我们有更加优美的东西:Kruskal重构树

就是按照边权排序,然后做Kruskal
如果需要加边,那么我们新建一个结点,权值设为这条边的权值
然后把原来的两个点连到这个点下面当儿子
于是任意两点之间的最大边权就是他们的LCA的权值了

对于原题来讲,我们可以先倍增到最浅的可以到达的祖先
那么这个祖先的子树就是可以到达的所有点了
考虑DFS序,问题变为区间k大,这是主席树的经典应用
于是这题就做完啦!

今天上午考试考了一道类似的题目,然而忘了这个做法
是用的线段树合并+标记永久化,虽然能A但显然不如这个优雅

【Codeforces 741D】Arpa’s letter-marked tree and Mehrdad’s Dokhtar-kosh paths

相关链接

题目传送门:http://codeforces.com/contest/741/problem/D
中文题面:https://blog.sengxian.com/solutions/cf-741d

解题报告

看起来这个串的定义非常强的样子,但仔细观察不难发现,就是出现次数为奇数的字母最多出现一个
于是我们定时一个二进制状态$f_{i,j}$表示$i$到$j$这段路径中哪些字符出现了奇数次

我们考虑在每一条合法路径的LCA处将其统计
于是就变成了子树相关问题,于是非常自然想到启发式合并

考虑从子树最大的儿子那里继承集合,其他的儿子的集合暴力加入
因为走一条边,需要异或一个值,整个集合的转移我们可以记录一个标记,然后在插入时使其生效
考虑统计的话,我们在暴力插入的时候,枚举是哪一位不同,单次查询是$O(22)$的
又因为加入是$O(1)$的,所以总的时间复杂度$O(n \log n \cdot 22)$

【LA 5524】[EC2015 Final] Suffixes and Palindromes

相关链接

题目传送门:https://icpcarchive.ecs.baylor.edu/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=5524

题目大意

给出一个串的$sa$数组和$manacher$求出的那个数组
请你构造出一个字典序最小的符合条件的串,可能无解

有$T(T \le 100)$组数据
数组长度$\le 10^5$
时间限制:$4s$

背景

Claris给我安利的题,似乎子问题都会的样子
但合起来就不会了

解题报告

考虑$manacher$的那个数组,实际上是给出了$O(n)$条信息
每一条信息形如:第$i$个字符(不)等于第$j$个字符
如果只有这个条件的话,我们可以搞成几个大连通块,然后贪心

考虑$sa$数组,上午才做了这个题
也可以直接贪心

但这两货直接合在一起,似乎是给出一个差分约束系统
然后让你输出一组可行解,还要让你字典序最小
于是就不会了

但我们还是考虑直接在$SA$数组上贪心
考虑回文等价的影响的话,那实际上是填一位就会影响后面的很多位
似乎想一想,直接贪心会不会使一个区间的两头被值域给卡住
但仔细想一想,即使你给那一个等价类一个大一点的字符,区间的差分序列没有变啊
该卡住的还是会卡住,没被卡住的也不会有问题

于是我们任然在SA数组上贪心,考虑能不能与前一个数相等的时候
不仅考虑$rank$数组的影响,我们还需要考虑回文等价的影响
于是这题就这么做完啦,仍然是一个$O(n)$的贪心

【BZOJ 3917】[Baltic2014] sequence

相关链接

题目传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=3917
神犇题解Ⅰ:http://blog.csdn.net/lcrtest/article/details/51312981
神犇题解Ⅱ:http://blog.csdn.net/neither_nor/article/details/52604754

吐槽

他们说这题是APIO 2016练习赛的题目,为什么我一点印象都没有 QwQ
想了很久都只会$O(\frac{n^2}{64})$的暴力,我也很绝望啊…..

解题报告

我们发现,对于比较高的位数来讲,其一样的数在一起
考虑将一样的数合在一起,并且从低位到高位处理

记录二进制状态$f_{i,j}$表示在第$i$位的时候,第$j$块里需要哪些数
如果当前只需要一个数了,那么就可以直接返回辣!(如果是0需要特判)
否则的话,我们枚举这一位在第一个数中是多少
因为下一层中连续的区间长度大概都比现在这一位连续的区间长10倍
所以我们可以把这一位中的状态每十个合成一块,下一层的话又是一个递归子问题,我们可以递归下去
考虑每一层的总复杂度:第$i$层有$10^i$个分叉,每个分叉内有$\frac{n}{10^i}$个块,于是每一层的复杂度是$O(n)$的
下面我们来总复杂度$T(n)=10 \cdot T(\frac{n}{10}) + n$
根据主定理,这货是$O(n \log{n})$的

【BZOJ 3897】Power

相关链接

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

解题报告

我们考虑给定一个区间$[l,r]$的起始体力值和最终体力值
那么显然我们应该把尽量多的体力值分配到收益最大的那一天$i$
然后$(i,r]$那一部分成了递归子问题,直接分治下去就可以了

至于前面那一部分,如果一直蓄力都不会满,那么就一直蓄力
否则的话,假设从第$j$天开始蓄力会满掉那么我们递归$[l,j-1]$即可
于是我们撸一发分治就可以了,时间复杂度:$O(n)$

【BZOJ 4319】[CERC2008] Suffix Reconstruction

相关链接

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

解题报告

之前在做这个题的时候就听说过这题
但当时想了一会儿,感觉不可做,以为是他们记错题目了 QwQ
今天居然看到原题了,但想了很久,还是只会$O(26 \cdot n \log{n})$

先来说说$O(26 \cdot n \log{n})$的做法吧!
我们可以从上到下单个字符填,就是尽量往小的填,填完以后$O(n)$验证一遍

我们考虑分出不同的地方,因为填了都是比他小的,所以如果冲突,那一定是之前的太大了
但之前的部分已经贪心尽量小地填了,所以之前的肯定不能改小了,只能把当前位改大

所以这么做是对的,时间复杂度:$O(26 \cdot n^2)$
不难发现$SA$数组上一定是一段连续的A,之后一段连续的B。后面也是这样
于是我们可以二分每一个字符地分界点,这样复杂度就是$O(26 \cdot n \log{n})$的了

正解的话,是$O(n)$的,仍然是从小到大贪心
我们考虑计算$height$数组时候那个Trick的正确性证明
如果$rank[sa[i]+1] > rank[sa[i+1]+1]$的话,那么$sa[i]$填的字符一定比$sa[i+1]$小
否则他俩相等也没有关系,因为后面还是会把他们区分出来
于是贪心就可以$O(1)$验证了,于是总的时间复杂度就是线性的了!

Code

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

const int N = 500009;

int n,sa[N],rak[N];
char ans[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;
}

int main() {
	n = read(); 
	for (int i=1;i<=n;i++) rak[sa[i]=read()]=i;
	ans[sa[1]] = 'a';
	for (int i=1,w='a';i<n;i++) (rak[sa[i]+1]>rak[sa[i+1]+1])? ans[sa[i+1]]=++w: ans[sa[i+1]]=w;
	if (ans[n] <= 'z') printf("%s",ans+1);
	else puts("-1");
	return 0;
}

【BZOJ 3711】[PA2014] Druzyny

相关链接

题目传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=3711
神犇题解Ⅰ:http://www.cnblogs.com/clrs97/p/4654215.html
神犇题解Ⅱ:http://blog.csdn.net/u010600261/article/details/54917569

解题报告

去膜了题解,好神啊!
本题一个区间要同时受$c_i,d_i$的限制

于是我们先只考虑$d_i$的限制
那么对于某一个区间的右端点$i$来讲,设合法的左端点$\in [g_i,i)$
至于$g_i$怎么求?搞一个队列就可以了?或者二分也是可以的
另外还需要注意一点:$g_i$是单调递增的

现在我们再来考虑$c_i$的限制,我们使用分治来解决
在$solve(l,r)$的时候,我们找出$c_i$最大的那个点$k$,然后递归下去
在合并上来的时候,我们就只需要考虑$[l,k-1]$对于$[k,r]$的贡献了
更进一步:因为$c_k$是$[l,r]$中最大的那个,所以对于此时所有的转移,$c_i$的限制均只需要考虑$c_k$
那么此时对于$i \in [k,r]$来讲,其合法的左端点$j \in [max(l,g_i),min(k-1,i-c_k)]$
因为$g_i$单调递增,所以我们对于$[k,r]$从左到右分四段考虑:

  1. $g_i \le l$且$i-c_k \le k-1$
    我们的首先我们肯定可以使用线段树来更新
    但更进一步,对于这一类点,我们只需要在查询第一个点时使用线段树就可以了
    因为这是一个前缀,之后$i$每向右移一位,合法的$j$也最多增加$1$
    不难发现,总的暴力操作次数不大于左右区间中较小的一段
    时间复杂度:$O(\log + \min(r-k+1,k-l))$
  2. $g_i \le l$且$k-1 < i-c_k$
    对于这些点,我们查询的是整个左区间
    我们整体查一次,然后一起更新就可以了
    时间复杂度:$O(\log n)$
  3. $l < g_i < k$
    对于这些点,我们查询的是左区间的一个后缀,我们直接在线段树上查就好
    考虑所有会影响到$i$的$solve$,它们的左区间一定没有交集
    也就是说只会有一个$solve$的左区间包含$g_i$
    于是对于每一个$i$,在整个算法中只会被查询一次
    所以这部分复杂度是$O(n \log n)$的,且不需要考虑到分治的复杂度中去
  4. $g_i \le k$
    直接不管就好

现在我们来分析分治的复杂度:$T(a+b)=T(a)+T(b)+min(a,b)+\log n$
我们发现这和启发式合并一样,于是复杂度是$O(n \log n)$的
在算上第三类更新的复杂度,总时间复杂度仍然为$O(n \log n)$

值得一提的是,这种与最值相关的问题使用分治来解决已经多次出现
比如HDU 5575,一定要引起重视啊

【日常小测】资源采集

题目大意

给定一棵$n(n \le 52501)$个点的树,初始状态下,每一结点存储的能量为$0$
树上每一个点有两个属性$v_i,l_i$分别表示这个点每单位时间产生的能量,和这个点的存储能量的上限
再给定$q(q\le52501)$个操作,每个操作有三个属性$w_i,d_i,t_i$,表示
在$t_i$的时刻,遍历在$w_i$的子树中且与$w_i$距离不超过$d_i$的点,并收集这些点的能量
一个点的能量被收集后就会清空,询问每次操作能够收集到多少能量

解题报告

先来说一说链上的情况,我们定义$last_i$为$i$号点上一次被收集的时间
我们可以把相邻的且$last_i$相同的点合并在一起,然后询问的时候用函数式线段树来回答
如果还有什么问题的话,我们可以戳这个题:市场

现在考虑树上的情况,我们先搞出DFS序,将其作为一个点的横坐标
然后我们把深度作为这个点的纵坐标,然后把这个点扔到平面上
这样单次操作就是对于一个平面上的矩形进行操作
这个是经典的$Kd-Tree$的应用,可以做到单次操作只影响到$\sqrt{n}$个结点
于是将$Kd-Tree$子树中$last_i$相同的点合在一起,打一个标记
单次操作最多增加$\sqrt n$个标记,而每一次在$Kd-Tree$上走一次就会回收一个标记
于是总的操作次数为$q\sqrt n$的
当然我们也需要用平衡树的启发式合并,或者函数式线段树来支持询问
于是总的时间复杂度为:$O(q \sqrt n \log n)$

不过众所周知,这题的暴力是$O(nq)$的
所以非递归的暴力跑得飞快,两秒的题目暴力可以卡到一秒以内
所以考完改题的时候,所有改了的人都是用的暴力……