1点分治
//给一棵树,每条边有权。求一条简单路径,权值和等于 kk,且边的数量最小。
//第一行包含两个整数 n,k,表示树的大小与要求找到的路径的边权和。
//接下来的n-1行,每行三个整数u, v, w。两个点,和它们的边权
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<map>
#include<cmath>
#include<vector>
#include<queue>
#include<cstring>
#include<string>
#include<bitset>
using namespace std;
typedef long long ll;
const int maxn = 2e5 + 10;
const int inf = 1e6 + 10;
struct node {
int v, dis, Next;
}edge[maxn << 1];
int head[maxn], siz[maxn], maxp[maxn], dis[maxn];
int rem[maxn], num[maxn], judge[inf], q[maxn], vis[maxn];
int tot, sum, root, ans = inf, n, k;
void add(int u, int v, int dis) {
edge[++tot].dis = dis;
edge[tot].v = v;
edge[tot].Next = head[u];
head[u] = tot;
}
//找出重心
void getroot(int u, int fa) {
siz[u] = 1; maxp[u] = 0;
for (int i = head[u]; i; i = edge[i].Next)
{
int v = edge[i].v;
if (vis[v] || fa == v)continue;
getroot(v, u);
siz[u] += siz[v];
maxp[u] = max(maxp[u], siz[v]);
}
maxp[u] = max(maxp[u], sum - siz[u]);
if (maxp[u] < maxp[root])root = u;
}
//计算dis
void getdis(int u, int fa, int cnt) {
if (dis[u] > k)return;
ans = min(ans, judge[k - dis[u]] + cnt);
rem[++rem[0]] = dis[u]; num[rem[0]] = cnt;
for (int i = head[u]; i; i = edge[i].Next)
{
int v = edge[i].v;
if (vis[v] || fa == v)continue;
dis[v] = dis[u] + edge[i].dis;
getdis(v, u, cnt + 1);
}
}
//最难的地方,计算答案
void calc(int u) {
int p = 0;
for (int i = head[u]; i; i = edge[i].Next)
{
int v = edge[i].v;
if (vis[v])continue;
rem[0] = 0; dis[v] = edge[i].dis;//rem[0]代表数量
getdis(v, u, 1);
for (int j = 1; j <= rem[0]; j++)
{
q[++p] = rem[j];
judge[rem[j]] = min(judge[rem[j]], num[j]);
}
}
for (int i = 1; i <= p; i++)
judge[q[i]] = inf;
}
//找出所有的可能的解
void solve(int u) {
vis[u] = 1; judge[0] = 0;
calc(u);
for (int i = head[u]; i; i = edge[i].Next)
{
int v = edge[i].v;
if (vis[v])continue;
sum = siz[v]; root = 0;
maxp[0] = inf;
getroot(v, 0);
solve(root);
}
}
int main()
{
int i, u, v, dis;
scanf("%d%d", &n, &k);
for (i = 1; i < n; i++)
{
scanf("%d%d%d", &u, &v, &dis);
add(u, v, dis); add(v, u, dis);
}
for (i = 0; i <= 1e6; i++)judge[i] = inf;
sum = n; maxp[0] = inf;
getroot(1, 0);
solve(root);
if (ans == inf)printf("-1\n");
else printf("%d\n", ans);
return 0;
}
2扫描线模板
1 求面积
//经典的给出n个矩形, 求矩形面积并
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<queue>
#include<algorithm>
#include<map>
#include<iomanip>
#define INF 99999999
using namespace std;
const int MAX=200+10;
int mark[MAX<<2];//记录某个区间的下底边个数
double sum[MAX<<2];//记录某个区间的下底边总长度
double hash[MAX];//对x进行离散化,否则x为浮点数且很大无法进行线段树
//以横坐标作为线段(区间),对横坐标线段进行扫描
//扫描的作用是每次更新下底边总长度和下底边个数,增加新面积
struct seg{//线段
double l,r,h;
int d;
seg(){}
seg(double x1,double x2,double H,int c):l(x1),r(x2),h(H),d(c){}
bool operator<(const seg &a)const{
return h<a.h;
}
}s[MAX];
void Upfather(int n,int left,int right){
if(mark[n])sum[n]=hash[right+1]-hash[left];//表示该区间整个线段长度可以作为底边
else if(left == right)sum[n]=0;//叶子结点则底边长度为0(区间内线段长度为0)
else sum[n]=sum[n<<1]+sum[n<<1|1];
}
void Update(int L,int R,int d,int n,int left,int right){
if(L<=left && right<=R){//该区间是当前扫描线段的一部分,则该区间下底边总长以及上下底边个数差更新
mark[n]+=d;//更新底边相差差个数
Upfather(n,left,right);//更新底边长
return;
}
int mid=left+right>>1;
if(L<=mid)Update(L,R,d,n<<1,left,mid);
if(R>mid)Update(L,R,d,n<<1|1,mid+1,right);
Upfather(n,left,right);
}
int search(double key,double* x,int n){
int left=0,right=n-1;
while(left<=right){
int mid=left+right>>1;
if(x[mid] == key)return mid;
if(x[mid]>key)right=mid-1;
else left=mid+1;
}
return -1;
}
int main(){
int n,num=0;
double x1,x2,y1,y2;
while(cin>>n,n){
int k=0;
for(int i=0;i<n;++i){
cin>>x1>>y1>>x2>>y2;
hash[k]=x1;
s[k++]=seg(x1,x2,y1,1);
hash[k]=x2;
s[k++]=seg(x1,x2,y2,-1);
}
sort(hash,hash+k);
sort(s,s+k);
int m=1;
for(int i=1;i<k;++i)//去重复端点
if(hash[i] != hash[i-1])hash[m++]=hash[i];
double ans=0;
//memset(mark,0,sizeof mark);
//memset(sum,0,sizeof sum);如果下面是i<k-1则要初始化,因为如果对第k-1条线段扫描时会使得mark,sum为0才不用初始化的
for(int i=0;i<k;++i){//扫描线段
int L=search(s[i].l,hash,m);
int R=search(s[i].r,hash,m)-1;
Update(L,R,s[i].d,1,0,m-1);//扫描线段时更新底边长度和底边相差个数
ans+=sum[1]*(s[i+1].h-s[i].h);//新增加面积
}
printf("Test case #%d\nTotal explored area: %.2lf\n\n",++num,ans);
}
return 0;
}
/*
这里注意下扫描线段时r-1:int R=search(s[i].l,hash,m)-1;
计算底边长时r+1:if(mark[n])sum[n]=hash[right+1]-hash[left];
解释:假设现在有一个线段左端点是l=0,右端点是r=m-1
则我们去更新的时候,会算到sum[1]=hash[mid]-hash[left]+hash[right]-hash[mid+1]
这样的到的底边长sum是错误的,why?因为少算了mid~mid+1的距离,由于我们这利用了
离散化且区间表示线段,所以mid~mid+1之间是有长度的,比如hash[3]=1.2,hash[4]=5.6,mid=3
所以这里用r-1,r+1就很好理解了
*/
2求周长
#define mem(a,x) memset(a,x,sizeof(a))
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#include<set>
#include<stack>
#include<cmath>
#include<map>
#include<stdlib.h>
#include<cctype>
#include<string>
using namespace std;
typedef long long ll;
const int N = 5007;
const int X = 20007;
const int inf = 1<<29;
struct Edge//扫描线
{
int l,r;//左右端点的横坐标
int h;//这条线的高度,即纵坐标
int f;//标记这条边是上边(-1)还是下边(1)
}e[N*2];
bool cmp(Edge a,Edge b)
{
return a.h < b.h;//高度从小到大排,扫描线自下而上扫
}
struct Node
{
int l,r;//该节点代表的线段的左右端点坐标
int len;//这个区间被覆盖的长度
int s;//表示这个区间被重复覆盖了几次
bool lc,rc;//表示这个节点左右两个端点是否被覆盖(0表示没有被覆盖,1表示有被覆盖)
int num;//这个区间有多少条线段(这个区间被多少条线段覆盖)
//len用来计算横线 num用来计算竖线
}q[4*X];
#define ls i<<1
#define rs i<<1|1
#define m(i) ((q[i].l + q[i].r)>>1)
void pushup(int i)//区间合并
{
if (q[i].s)//整个区间被覆盖
{
q[i].len = q[i].r - q[i].l + 1;
q[i].lc = q[i].rc = 1;
q[i].num = 1;
}
else if (q[i].l == q[i].r)//这是一个点而不是一条线段
{
q[i].len = 0;
q[i].lc = q[i].rc = 0;
q[i].num = 0;
}
else //是一条没有整个区间被覆盖的线段,合并左右子的信息
{
q[i].len = q[ls].len + q[rs].len ;//长度之和
q[i].lc = q[ls].lc;q[i].rc = q[rs].rc;//和左儿子共左端点,和右儿子共右端点
q[i].num = q[ls].num + q[rs].num - (q[ls].rc&q[rs].lc);
//如果左子的右端点和右子的左端点都被覆盖了
}
}
void build (int i,int l,int r)
{
q[i].l = l,q[i].r = r;
q[i].s = q[i].len = 0;
q[i].lc = q[i].rc = q[i].num = 0;
if (l == r) return;
int mid = m(i);
build(ls,l,mid);
build(rs,mid+1,r);
}
void update(int i,int l,int r,int xx)
{
if (l == q[i].l && q[i].r == r)
{
q[i].s += xx;
pushup(i);
return;
}
int mid = m(i);
if (r <= mid) update(ls,l,r,xx);
else if (l > mid) update(rs,l,r,xx);
else
{
update(ls,l,mid,xx);
update(rs,mid+1,r,xx);
}
pushup(i);
}
int main()
{
int n;
while (cin>>n)
{
int x1,x2,y1,y2,mx = -inf,mn = inf;
int tot = 0;
for (int i = 0;i < n;++i)
{
scanf("%d %d %d %d",&x1,&y1,&x2,&y2);
mx = max(mx,max(x1,x2));
mn = min(mn,min(x1,x2));
Edge & t1 = e[tot];Edge & t2 = e[tot+1];
t1.l = t2.l = x1,t1.r = t2.r = x2;
t1.h = y1;t1.f = 1;
t2.h = y2;t2.f = -1;
tot += 2;
}
sort(e,e+tot,cmp);
//数据小可以不离散化
int ans = 0;//计算周长
int last = 0;//保存上一次的总区间的被覆盖的长度
build(1,mn,mx-1);
//每两条横线之间才会有竖线
for (int i = 0;i < tot;++i)
{
update(1,e[i].l,e[i].r-1,e[i].f);//根据扫描线更新
//计算周长
//横线:现在这次总区间被覆盖的程度和上一次总区间被覆盖的长度之差的绝对值
ans += abs(q[1].len - last);
//竖线:[下一条横线的高度-现在这条横线的高度]*2*num
ans += (e[i+1].h - e[i].h)*2*q[1].num;
last = q[1].len;//每次都要更新上一次总区间覆盖的长度
}
printf("%d\n",ans);
}
return 0;
}
3.线段树
1 。区间修改
void down(int k)
{
tree[k*2].f+=tree[k].f;
tree[k*2+1].f+=tree[k].f;
tree[k*2].w+=tree[k].f*(tree[k*2].r-tree[k*2].l+1);
tree[k*2+1].w+=tree[k].f*(tree[k*2+1].r-tree[k*2+1].l+1);
tree[k].f=0;
}
//
void add(int k)
{
if(tree[k].l>=a&&tree[k].r<=b)//当前区间全部对要修改的区间有用
{
tree[k].w+=(tree[k].r-tree[k].l+1)*x;//(r-1)+1区间点的总数
tree[k].f+=x;
return;
}
if(tree[k].f) down(k);//懒标记下传。只有不满足上面的if条件才执行,所以一定会用到当前节点的子节点
int m=(tree[k].l+tree[k].r)/2;
if(a<=m) add(k*2);
if(b>m) add(k*2+1);
tree[k].w=tree[k*2].w+tree[k*2+1].w;//更改区间状态
}
//区间查询和单点查询,和原来一样,只不过需要调用一个down操作
void sum(int k)//区间查询
{
if(tree[k].l>=x&&tree[k].r<=y)
{
ans+=tree[k].w;
return;
}
if(tree[k].f) down(k)//懒标记下传,唯一需要更改的地方
int m=(tree[k].l+tree[k].r)/2;
if(x<=m) sum(k*2);
if(y>m) sum(k*2+1);
}
3。染色
#pragma warning(disable:4996)
#include<iostream>
#include<cstring>
#include<cstdio>
#include<set>
#include<algorithm>
#include<climits>
using namespace std;
typedef long long ll;
const int maxn = 20005;
int inl[maxn], inr[maxn],vis[maxn];
int a[maxn << 2],ans;
struct node
{
int l, r, c;
};
node tree[maxn << 4];
void build(int L, int R, int k)
{
tree[k].l = L;tree[k].r = R;
tree[k].c = 0;
if (L == R)return;
int mid = (L + R) / 2;
build(L, mid, 2 * k);
build(mid + 1, R, 2 * k + 1);
}
//建树
void update(int L, int R, int c, int k)
{
if (L == tree[k].l && R == tree[k].r)
{
tree[k].c = c;
return;
}
if (tree[k].c)
{
tree[2 * k].c = tree[k].c;
tree[2 * k + 1].c = tree[k].c;
tree[k].c = 0;
}
int mid = (tree[k].l + tree[k].r) / 2;
if (R <= mid)update(L, R, c, 2 * k);
else if (L > mid)update(L, R, c, 2 * k + 1);
else
{
update(L, mid, c, 2 * k);
update(mid + 1, R,c, 2 * k + 1);
}
}
//更新
void query(int L, int R, int k)
{
if (tree[k].c)
{
if (!vis[tree[k].c])
{
ans++;
vis[tree[k].c] = 1;
}
return;
}
if (tree[k].l == tree[k].r)return;
int mid = (tree[k].l + tree[k].r) / 2;
if (R <= mid)query(L, R, 2 * k);
else if (L > mid)query(L, R, 2 * k + 1);
else
{
query(L, mid, 2 * k);
query(mid + 1, R, 2 * k + 1);
}
}
int main()
{
int T, i, j, n,t;
scanf("%d", &T);
while (T--)
{
scanf("%d", &n);
j = 0;
for (i = 1;i <= n;i++)
{
scanf("%d%d", &inl[i], &inr[i]);
a[j++] = inl[i];
a[j++] = inr[i];
}
//把左右区间的数值输入,a数组下标从0到j-1
sort(a, a + j);
j=unique(a, a + j) - a;
//先排序再去重
t = j;
for (i = 1;i < t;i++)
{
if (a[i] > (a[i - 1] + 1))
{
a[j++] = a[i - 1] + 1;
}
}
//当相邻两项的差值大于1的时候,需要在中间加一个数
//直接先加在数组的最后,然后再排序
sort(a, a + j);
memset(tree, 0, sizeof(tree));
memset(vis, 0, sizeof(vis));
//此时的j代表元素的个数,也是区间长度,线段树是从1开始的
//所以,build(1,j,1)
build(1, j, 1);
//cout << " J " << j << endl;
for (i = 1;i <= n;i++)
{
inl[i] = lower_bound(a, a + j, inl[i]) - a + 1;
inr[i] = lower_bound(a, a + j, inr[i]) - a + 1;
//cout << inl[i] << " " << inr[i] << endl;
update(inl[i], inr[i], i, 1);
}
ans = 0;
query(1, j, 1);
printf("%d\n", ans);
}
return 0;
}
3.区间最大子段和
//n个数,m个询问,每次询问l,r区间的最大子段和
#include<cstdio>
#include<algorithm>
#include<iostream>
using namespace std;
int n,m,opl,opr,cnt;
struct node
{
int l,r;
long long max,max_l,max_r,sum;
}e[4000001];
void query(int k,long long & ans,long long & ans_l,long long & ans_r)
{
if(e[k].l>=opl&&e[k].r<=opr)
{
ans=e[k].max;
ans_l=e[k].max_l;
ans_r=e[k].max_r;
return;
}
int mid=e[k].l+e[k].r>>1;
if(opr<=mid) query(k<<1,ans,ans_l,ans_r);
else if(opl>mid) query((k<<1)+1,ans,ans_l,ans_r);
else
{
long long lch_max=0,lch_max_r=0,rch_max=0,rch_max_l=0,lch_max_l=0,rch_max_r=0;
query(k<<1,lch_max,lch_max_l,lch_max_r);
query((k<<1)+1,rch_max,rch_max_l,rch_max_r);
ans=max(lch_max,rch_max);
ans=max(ans,lch_max_r+rch_max_l);
ans_l=max(lch_max_l,e[k<<1].sum+rch_max_l);
ans_r=max(rch_max_r,e[(k<<1)+1].sum+lch_max_r);
}
}
void unionn(int k)
{
e[k].max=max(max(e[k<<1].max,e[(k<<1)+1].max),e[k<<1].max_r+e[(k<<1)+1].max_l);
e[k].max_l=max(e[k<<1].max_l,e[k<<1].sum+e[(k<<1)+1].max_l);
e[k].max_r=max(e[(k<<1)+1].max_r,e[(k<<1)+1].sum+e[k<<1].max_r);
e[k].sum=e[k<<1].sum+e[(k<<1)+1].sum;
}
void build(int k,int l,int r)
{
e[k].l=l,e[k].r=r;
if(l==r)
{
cin>>e[k].max;
e[k].max_l=e[k].max_r=e[k].sum=e[k].max;
return;
}
int mid=l+r>>1;
build(k<<1,l,mid);
build((k<<1)+1,mid+1,r);
unionn(k);
}
int main()
{
scanf("%d",&n);
build(1,1,n);
scanf("%d",&m);
for(int i=1;i<=m;i++)
{
scanf("%d%d",&opl,&opr);
long long ans=0,ans_l=0,ans_r=0;
query(1,ans,ans_l,ans_r);
printf("%lld\n",ans);
}
}
后缀数组
后缀数组和二分求N个字符串的最长公共子串
* Memo: 多串最长公共子串 后缀数组 二分答案
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstring>
const int MAXL=10011,MAXN=6;
using namespace std;
struct SuffixArray
{
struct RadixElement
{
int id,k[2];
}RE[MAXL],RT[MAXL];
int N,A[MAXL],SA[MAXL],Rank[MAXL],Height[MAXL],C[MAXL];
void RadixSort()
{
int i,y;
for (y=1;y>=0;y--)
{
memset(C,0,sizeof(C));
for (i=1;i<=N;i++) C[RE[i].k[y]]++;
for (i=1;i<MAXL;i++) C[i]+=C[i-1];
for (i=N;i>=1;i--) RT[C[RE[i].k[y]]--]=RE[i];
for (i=1;i<=N;i++) RE[i]=RT[i];
}
for (i=1;i<=N;i++)
{
Rank[ RE[i].id ]=Rank[ RE[i-1].id ];
if (RE[i].k[0]!=RE[i-1].k[0] || RE[i].k[1]!=RE[i-1].k[1])
Rank[ RE[i].id ]++;
}
}
void CalcSA()
{
int i,k;
RE[0].k[0]=-1;
for (i=1;i<=N;i++)
RE[i].id=i,RE[i].k[0]=A[i],RE[i].k[1]=0;
RadixSort();
for (k=1;k+1<=N;k*=2)
{
for (i=1;i<=N;i++)
RE[i].id=i,RE[i].k[0]=Rank[i],RE[i].k[1]=i+k<=N?Rank[i+k]:0;
RadixSort();
}
for (i=1;i<=N;i++)
SA[ Rank[i] ]=i;
}
void CalcHeight()
{
int i,k,h=0;
for (i=1;i<=N;i++)
{
if (Rank[i]==1)
h=0;
else
{
k=SA[Rank[i]-1];
if (--h<0) h=0;
for (;A[i+h]==A[k+h];h++);
}
Height[Rank[i]]=h;
}
}
}SA;
int N,Ans,Bel[MAXL];
char S[MAXL];
void init()
{
int i;
freopen("pow.in","r",stdin);
freopen("pow.out","w",stdout);
scanf("%d",&N);
SA.N=0;
for (i=1;i<=N;i++)
{
scanf("%s",S);
for (char *p=S;*p;p++)
{
SA.A[++SA.N]=*p-'a'+1;
Bel[SA.N]=i;
}
if (i<N)
SA.A[++SA.N]=30+i;
}
}
bool check(int A)
{
int i,j,k;
bool ba[MAXN];
for (i=1;i<=SA.N;i++)
{
if (SA.Height[i]>=A)
{
for (j=i;SA.Height[j]>=A && j<=SA.N;j++);
j--;
memset(ba,0,sizeof(ba));
for (k=i-1;k<=j;k++)
ba[Bel[SA.SA[k]]]=true;
for (k=1;ba[k] && k<=N;k++);
if (k==N+1)
return true;
i=j;
}
}
return false;
}
void solve()
{
int a,b,m;
SA.CalcSA();
SA.CalcHeight();
a=0;b=SA.N;
while (a+1<b)
{
m=(a+b)/2;
if (check(m))
a=m;
else
b=m-1;
}
if (check(b))
a=b;
Ans=a;
}
int main()
{
init();
solve();
printf("%dn",Ans);
return 0;
}
一个串中不可重叠的重复最长子串
public class MaxRepeatSubString2 {
public static void main(String[] args) {
int res = maxRepeatSubString2("1x23231923263");
System.out.println(res); // 输出 3
}
/**
* 不允许交叉
*
* @param src
* @return
*/
public static int maxRepeatSubString2(String src) {
SuffixArray.Suff[] sa = SuffixArray.getSa2(src);
int[] height = SuffixArray.getHeight(src, sa);
int l = 0;
int r = height.length;
int ans = 0;
while (l <= r) {
int mid = l + ((r - l) >> 1);// check的重叠长度
if (check(height, sa, mid)) {
if (mid == height.length / 2) {
return mid;
}
l = mid + 1;
ans = mid;
// return mid;
} else {
r = mid - 1;
}
}
return ans;
}
/**
* 用len将height分组,小于组和大于等于组交替
* 在大于组中更新最大最小原始小标,大转小的时候检查上一个大于组是否满足不重叠
* 在小于组中,只需持续地将原始下标付给max和min,这样小转大的时候,可以保留小于组最后一个元素的下标
*/
private static boolean check(int []height,SuffixArray.Suff[]sa,int len){
int minIndex = sa[0].index;
int maxIndex = sa[0].index;
for(int i = 1;i<height.length;i++){
int index = sa[i].index;
if(height[i]>=len){ // lcp 大于 len
minIndex = Math.min(minIndex,index);
maxIndex = Math.max(maxIndex, index);
} else {
if (maxIndex - minIndex >= len) {
return true;
}
maxIndex = index;
minIndex = index;
}
}
return (maxIndex - minIndex) >= len;
}
}
//逆元求排列组合
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const ll mod=1e9+7;
const int maxn=1e5+7;
ll fac[maxn];
ll inv[maxn];
ll C(int m,int n)
{
if(m>n)
return -1;
return fac[n]*inv[m]%mod*inv[n-m]%mod;
}
ll quick_mod(ll a,ll m)
{
ll tmp=a%mod;
ll ans=1;
while(m)
{
if(m&1)
ans=ans*tmp%mod;
tmp=tmp*tmp%mod;
m>>=1;
}
return ans;
}
void init()
{
fac[0]=1;
for(int i=1; i<maxn; i++)
fac[i]=(fac[i-1]*i)%mod;
inv[maxn-1]=quick_mod(fac[maxn-1],mod-2);
for(int i=maxn-2; i>=0; i--)
inv[i]=(inv[i+1]*(i+1))%mod;
}
int main()
{
init();
ll n,m;
while(cin>>n>>m)
cout << C(m,n) << endl;
return 0;
}