初一上 合集

P1552 【[APIO2012]派遣】

我们看到这道题的第一眼,第一思路是贪心,用左偏树合并子树的堆,不断取最小点,使总和不超过m

但是,这么考虑是没有前途的,实现比较困难。所以我们考虑删点,删去大的点使其符合条件。

好了,思路说完了,我来讲一下实现细节:因为考虑到父亲编号小于自己,所以时间戳对应的编号是单调上升的,我们就从n~1每次将自己的堆合并到爸爸中,然后不断删点使爸爸符合条件。由于每次进来的时候自己已经被自己所有儿子做过了,所以直接更新ans。

#include<bits/stdc++.h>
using namespace std;
const int N=100001;
int n,m,val[N],f[N],fa[N],a[N],ls[N],rs[N],dis[N];
int cnt,h[N<<1],nxt[N<<1],to[N<<1];
void add(int x,int y){
    cnt++;
    nxt[cnt]=h[x];
    h[x]=cnt;
    to[cnt]=y;
}
long long ans;
int sum[N],rt[N],sz[N];
int merge(int x,int y){
	if(!x||!y)return x+y;
	if(val[x]<val[y])swap(x,y);
	rs[x]=merge(rs[x],y);
	if(dis[rs[x]]>dis[ls[x]])swap(rs[x],ls[x]);
	dis[x]=dis[rs[x]]+1;
	return x;
}
int pop(int x){
	int k=val[rt[x]];
	rt[x]=merge(ls[rt[x]],rs[rt[x]]);
	return k;
}
int main(){
    int i;
    cin>>n>>m;
    for(i=1;i<=n;i++)scanf("%d%d%d",&fa[i],&val[i],&a[i]),sum[i]=val[i],sz[i]=1,rt[i]=i;
    for(i=n;i>=1;i--){
    	rt[fa[i]]=merge(rt[fa[i]],rt[i]);
    	sz[fa[i]]+=sz[i];
    	sum[fa[i]]+=sum[i];
    	while(sum[fa[i]]>m){
    		sum[fa[i]]-=pop(fa[i]);
    		sz[fa[i]]--;
		}
		ans=max(ans,1ll*sz[i]*a[i]);
	}
	cout<<ans<<endl;
    return 0;
} 

CF8C 【Looking for Order】

虽然这道题暴力状压很好打,但是2n*n2的复杂度根本过不了。

但是你想想,对于所有操作的顺序是可以变的,答案不会变,因为我的状态中有这个东西,这东西不管是在什么时候捡的,它放到最后一次捡肯定没问题.

于是,对于任意在状态中的物品,我都可以用它来转移,所以我们任取一个物品,暴力枚举另外一个和它一起捡的,就可以通过此题了。

清蒸code

#include<bits/stdc++.h>
using namespace std;
int n,sx,sy,x[30],y[30],f[20000000],z[20000000];
int lowbit(int x){
    return x & -x;
} 
int dis(int u,int v){
    return (x[u]-x[v])*(x[u]-x[v])+(y[u]-y[v])*(y[u]-y[v]);
}
int H(int x){
    int cnt=0;
    while(x){
        cnt++;
        x>>=1;
    }
    return cnt;
} 
int main(){
    int i,j;
    cin>>x[0]>>y[0]>>n;
    for(i=1;i<=n;i++)scanf("%d%d",&x[i],&y[i]);
    memset(f,0x3f,sizeof(f));
    f[0]=0;
    for(int state=1;state<(1<<n);state++){
        i=lowbit(state); 
        int w=state;
        while(w){
            j=lowbit(w);
            int nw=f[state^(i|j)]+dis(0,H(i))+dis(H(i),H(j))+dis(H(j),0);
            if(nw<f[state]){
                f[state]=nw;
                z[state]=state^(i|j);
            }
            w-=lowbit(w);
        }
    }
    cout<<f[(1<<n)-1]<<endl;
    int pd=(1<<n)-1;
    while(pd){
        printf("0 ");
        int t1=pd^z[pd];
        int t2=lowbit(pd);
        t1-=t2;
        printf("%d ",H(lowbit(t2)));
        if(t1)printf("%d ",H(lowbit(t1)));
        pd=z[pd];
    } 
    printf("0\n");
    return 0;
}

CF126B 【Password】

首先一看到这道题,要判断字符串是否出现,最先想到kmp,但是单纯用kmp去匹配,是O(N^2)的时间复杂度,但是数据量却有1000000,肯定不行。

那么就得启发我们换换思路,是否一定要匹配呢?

换个角度思考,抛开字符串要在中间出现不谈,既然题目要求最长长度的前后缀,kmp的next数组不就是如此吗?

所以如果next[n]对应的字符串中间出现过,答案肯定就是。那么如何判断呢?

如果2n-1(为何不是1-n,因为题目中说了此串的第三次出现不能在前后缀位置)当中有串和1-next[n]相同,则意味着如果缩短1-i的字符串,把后几位扔掉,总会存在2n-1的一个i,它的后缀与1next[n]相同,但是1next[n]又是1~i的前缀,所以这就是前后缀相同了,只要判断next[i]==next[n]就行了

如果next[n]没有出现过3次,那么1next[n]的最长相同前后缀一定是答案(因为它长度最大,而且分别在1-next[n]、n-next[n]+1next[n]出现了2次,总共4次,满足条件)

29行代码搞定

#include<bits/stdc++.h>
using namespace std;
int n;
char s[1000001],s1[1000001];
int p[1000001],len,p1[1000001];
int main(){
    int i;
    scanf("%s",s+1);
    n=strlen(s+1);
    int j=0;
    for(i=2;i<=n;i++){
        while(j&&s[i]!=s[j+1])j=p[j];
        if(s[i]==s[j+1])j++;
        p[i]=j;
    }
    int x=p[n],y=n-p[n]+1;
    for(i=2;i<=n-1;i++)if(p[i]==p[n])len=max(len,p[n]);
    for(i=y;i<=n;i++)s1[i-y+1]=s[i];
    j=0;
    for(i=2;i<=p[n];i++){
        while(j&&s1[i]!=s1[j+1])j=p1[j];
        if(s1[i]==s1[j+1])j++;
        p1[i]=j;
    }
    if(p1[p[n]])len=max(len,p1[p[n]]);
    if(len)for(i=1;i<=len;i++)putchar(s[i]);
    else puts("Just a legend");
    return 0;
}

P3994 【高速公路】

(我申请好多次了, 求管理员大大给过吧)

首先,这道题,N^2暴力很好想吧!

F[i]=F[j]+(dep[i]-dep[j])p[i]+q[i](j为i的祖先)

上斜率优化,整理一下:F[i]+p[i]dep[j]=F[j]

所以,我们得拎出一条链来做单调队列,但是子孙的单调队列可以从父亲继承的。
那么我们可以在这个点最后要回溯(进入另一个子树)的时候将这个点的单调队列复原成父亲的。因为我们是用数组模拟队列的,所以我们只改动了3个值:head,tail 和 q[tail]。我们就直接复原回去就行了。

code:

#include<bits/stdc++.h>
using namespace std;
const int N=5000001;
typedef long long ll;
ll n;
ll cnt,h[N],nxt[N],to[N];
ll val[N],p[N],c[N] ;
void add(ll x,ll y,ll z){
	cnt++;
	nxt[cnt]=h[x];
	h[x]=cnt;
	to[cnt]=y,val[cnt]=z;
}
ll dep[N];
ll f[N];
ll X(ll x){
	return dep[x];
}
ll Y(ll x){
	return f[x];
}
double slope(ll u,ll v){
	return 1.0*(1.0*(Y(u)-Y(v)))/(1.0*(X(u)-X(v)));
} 
ll q[N];
ll he=1,ta=0;
void dfs(ll u,ll fa){
	ll i;
	ll qh=he,qt=ta;
	while(he<ta&&slope(q[he],q[he+1])<1.0*p[u])he++;
	f[u]=f[q[he]]+(dep[u]-dep[q[he]])*p[u]+c[u];
	while(he<ta&&slope(q[ta],q[ta-1])>slope(q[ta],u))ta--;
	ll ru=++ta;
	ll k=q[ru];q[ru]=u;
	for(i=h[u];i;i=nxt[i]){
		ll v=to[i];ll w=val[i];
		if(v!=fa)dep[v]=dep[u]+w,dfs(v,u);
	}
	he=qh,ta=qt,q[ru]=k;
}
int main(){
	cin>>n;
	ll i;
	for(i=2;i<=n;i++){
		ll y;ll z;
		scanf("%lld%lld%lld%lld",&y,&z,&p[i],&c[i]);
		add(y,i,z),add(i,y,z);
	}
	dfs(1,0);
	for(i=2;i<=n;i++)printf("%lld\n",f[i]);
	return 0;
}

P3648 【[APIO2014]序列分割】

这道题其实很简单,毕竟给了你一个友善的样例。。。。。。

我们可以自定义一个划分顺序,对于每个f[i],就是每次先将划去最后几个数(设划分点为p),他们产生的贡献等于(s[i]-s[p])*s[p],就可以转移到上一步的j了

具体来说,f[i][k]=max(f[j][k-1]+(s[i]-s[j])*s[j])

等等,这里有个k怎么办呀。定睛一看,k只与k-1有关,可以缩掉一维。

再定睛一看,这题k非常小,于是可以暴力枚举k,然后将上一次的f[i]记为g[i].

经过一堆繁琐的化简之后,柿子变成了这个东西:f[i]-s[i]s[j]=g[j]-s[j]^2

将f[i]当做截距,欲使截距最大,那么斜率递减,维护上凸壳,即将-s[i]当做斜率

完结撒花!(注意一下斜率不存在的时候,slope函数返回-1e8)

#include<bits/stdc++.h>
using namespace std;
const int N=100001;
typedef long long ll;
int n,k;
ll f[N],s[N],g[N];
inline ll X(int u){
	return s[u];
}
inline ll Y(int u){
	return g[u]-s[u]*s[u];
}
inline double slope(int u,int v){
	if(s[u]==s[v])return -1e8;//attention!!!
	return 1.0*((1.0*(Y(u)-Y(v)))/(1.0*(X(u)-X(v))));
}
int q[N],lst[N][201],a[N],cnt;
int main(){
	register int i;
	cin>>n>>k;
	for(i=1;i<=n;++i)scanf("%lld",&s[i]),s[i]+=s[i-1];
	for(int tim=1;tim<=k;++tim){
		int h=1,t=1;
		memset(q,0,sizeof(q));
		for(i=1;i<=n;++i){
			while(h<t&&slope(q[h],q[h+1])>=-s[i])h++;
			f[i]=g[q[h]]+s[q[h]]*(s[i]-s[q[h]]);
			lst[i][tim]=q[h];//记录路径
			while(h<t&&slope(q[t],q[t-1])<=slope(q[t-1],i))t--;
			q[++t]=i;
		}
		for(i=1;i<=n;++i)g[i]=f[i];
	}
	cout<<f[n]<<endl;
	int p=n;
	while(p){
		a[++cnt]=p;
		p=lst[p][k];
		k--;
	}
	for(i=cnt;i>=2;i--)printf("%d ",a[i]);
	return 0;
}

P3952 【时间复杂度 】

半年前当我还是小学生的时候,这道题写了3个小时都没写完,然后@bmh201708 告诉了我一个简单的方法,令我恍然大悟。

首先要明确:时间复杂度等于运行时间最长的那一层循环嵌套的时间

For example:

for(i=1;i<=n;i++){
	for(j=1;j<=n;j++)do_sth
	for(j=1;j<=1;j++)do_sth
}

时间复杂度是O(N^2)(忽略do_sth)

然后,我们在计算for(j=1;j<=1;j++)时,因为外面还有一层,所以要乘以n。关键是我们如何将上面一层的兄弟循环for(j=1;j<=n;j++)忽略呢?这个用栈记录一下就好了,因为在下一层入栈时上一层已经出栈,否则就是语法错误。还有,如果一层出栈时发现还在栈中的循环变量有重复,那么这一层不能被算进去。

实现的时候比较简单的方法:读入n,w用getchar大法。读入x,y的时候用string读,判断大小用一个check函数就行了(string自带判断大小函数请慎用!!!)

只有85行的清真code:

#include<bits/stdc++.h>
using namespace std;
int t;
struct node{
	char nm;
	int f;
}sta[10001];
int cnt;
int v[1001];
int check(string a,string b){
	int l1=a.size(),l2=b.size();
	if(l1<l2)return 0;
	if(l1>l2)return 1;
	for(int i=0;i<l1;i++){
		if(a[i]<b[i])return 0;
		if(a[i]>b[i])return 1;
	}
	return 2;
}
int main(){
	int i;
	cin>>t;
	while(t--){
		cnt=0;
		int n,w;
		char c;
		scanf("%d",&n);
		while(c=getchar())if(c=='('){
			break;
		}
		c=getchar();
		if(c=='n'){
			c=getchar();
			scanf("%d",&w);
			c=getchar();
		}else{
			scanf("%d",&w);
			c=getchar();
			w=0;
		}
		c=getchar();
		int ans=0,flag=0;
		while(n--){
			while(c=getchar()){
				if(c=='E'||c=='F')break;
			}
			if(c=='F'){
				cnt++;
				char p;
				string x,y;
				scanf(" %c",&p);
				cin>>x>>y;
				sta[cnt].nm=p;
				int ji=0;
				if(x=="n")x="1000";
				if(y=="n")y="1000";
				if(check(x,y)==1){
					ji=-1;
				}else{
					if(check(x,"1000")==0&&y=="1000")ji=1;
					if(check(x,"1000")==0&&check(y,"1000")==0)ji=0;
					if(x=="1000"&&y=="1000")ji=0;
				}	
				sta[cnt].f=ji;
			}else{
				if(cnt==0)flag=-1;
				int sum=0,jue=0;
				memset(v,0,sizeof(v));
				for(i=cnt;i>=1;i--){
					if(v[sta[i].nm])flag=-1;
					if(sta[i].f==-1)jue=1;
					else{
						v[sta[i].nm]=1;
						sum+=sta[i].f;
					}
				}
				if(jue==0)ans=max(ans,sum);
				cnt--;
			}
		}
		if(flag==-1||cnt>0)puts("ERR");
		else puts(ans==w?"Yes":"No");
	}
	return 0;
}

P3292 [SCOI2016]幸运数字

做完线性基♂的模板题,就来看这道题,我怕不是疯了。

实际上此题还是相当水的,只不过我们查询的是树上路径的线性基♂,而且线性基不能通过删除等操作实现挖取部分元素做线性基的操作,所以只能暴力合并了。。。。。

那当然不能一个一个元素地合并了,因为异或具有结合律,所以我可以按照倍增的思想,gay[i][j]表示i往上跳2^j步的线性基,然后dp的时候暴力合并两个gay[i][j-1]与gay[fa[i][j-1]][j-1],复杂度N(logN)*64。

在查询的时候类似倍增LCA,在往上跳的过程中暴力合并,复杂度也是N(logN)*64的

实现时我们可以将线性基的板子封装成结构体,代码会很短(仅97行)
最后提醒大家一句:开long long 开long long 开long long,重要的事情说三遍!!!

code:

#include<bits/stdc++.h>
using namespace std;
const int M=20001;
int n;
typedef long long ll;
ll a[100001];
struct xxj{
	ll b[64];
	xxj(){
		memset(b,0,sizeof(b));
	}
	void insert(ll x){
		int i;
		for(i=63;~i;i--){
			if(!((x>>i)&1))continue;
			if(!b[i]){b[i]=x;return ;}
			else x^=b[i];
		}
	}
	ll q_max(){
		int i;
		ll ans=0;
		for(i=63;~i;i--){
			ans=max(ans,ans^b[i]);
		}
		return ans;
	}
};
xxj merge(xxj u,xxj v){
	int i;
	for(i=63;~i;i--){
		if(v.b[i])u.insert(v.b[i]);
	}
	return u;
}
int cnt,nxt[2*M],h[2*M],v[2*M];
void add(int x,int y){
	cnt++;
	nxt[cnt]=h[x];
	h[x]=cnt;
	v[cnt]=y;
}
int fa[M][21],dep[M];
xxj gay[M][21];
void dfs(int u,int f){
	int i;
	fa[u][0]=f;
	gay[u][0].insert(a[f]);
	dep[u]=dep[f]+1;
	for(i=h[u];i;i=nxt[i]){
		int to=v[i];
		if(to!=f)dfs(to,u);
	}
}
ll query(int x,int y){
	int i;
	if(dep[x]>dep[y])swap(x,y);
	xxj jyk;
	jyk.insert(a[x]),jyk.insert(a[y]);
	for(i=20;i>=0;i--){
		if(dep[fa[y][i]]>=dep[x])jyk=merge(jyk,gay[y][i]),y=fa[y][i];
	}
	if(x==y)return jyk.q_max();
	for(i=20;i>=0;i--){
		if(fa[x][i]!=fa[y][i]){
			jyk=merge(jyk,gay[x][i]);
			jyk=merge(jyk,gay[y][i]);
			x=fa[x][i],y=fa[y][i];
		}
	}
	jyk=merge(jyk,gay[x][0]);
	return jyk.q_max();
}
int main(){
	int i,j;
	int q;
	cin>>n>>q;
	for(i=1;i<=n;i++)scanf("%lld",&a[i]);
	for(i=1;i<n;i++){
		int x,y;
		scanf("%d%d",&x,&y);
		add(x,y),add(y,x);
	}
	dfs(1,0);
	for(j=1;j<=20;j++){
		for(i=1;i<=n;i++){
			fa[i][j]=fa[fa[i][j-1]][j-1];
			gay[i][j]=merge(gay[fa[i][j-1]][j-1],gay[i][j-1]);
		}
	}
	while(q--){
		int x,y;
		scanf("%d%d",&x,&y);
		printf("%lld\n",query(x,y));
	}
	return 0;
}

CF940F 【Machine Learning】

这是一道带修莫队的模板题,具体细节不再赘述,但是有一些实现时候的技巧要注意一下。

对于每次的时光倒流、推移操作,首先得放在区间移动后面做(因为放在前面做改的就是上一个区间的值,但是修改对它是没有影响的)。网上有一种广为流传的做法是:swap(原值,修改值)
这样的话推移一次再倒流回来,结果是一样的。

还有,对于mex操作复杂度的证明,我有一种感性想法:Σmex[i]=n,并且对于每次移动区间,修改的mex[i]都不会很多,所以我认为mex[i]最后会趋*于*均化,也就是sqrt(n)级别的

code:

#include <bits/stdc++.h>   
#define N 300005  
#define ll long long  
#define setIO(s) freopen(s".in","r",stdin) 
using namespace std;  
int n,m,tot,opcnt,qcnt,B,now;      
int a[N],A[N],output[N],cnt[N],mex[N];        
struct query
{
    int l,r,id,t;  
    query(int l=0,int r=0):l(l),r(r){} 
    bool operator<(query b) const 
    {
        return l/B==b.l/B?(r/B==b.r/B?t<b.t:r<b.r):l<b.l;   
    }
}q[N]; 
struct change
{
    int p,x;   
    change(int p=0,int x=0):p(p),x(x){}  
}c[N];   
void add(int num) 
{   
    --mex[cnt[num]];       
    ++mex[++cnt[num]];     
}
void del(int num) 
{   
    --mex[cnt[num]];  
    ++mex[--cnt[num]];         
}
void update(int id,int t) 
{
    if(c[t].p>=q[id].l&&c[t].p<=q[id].r) 
    {
        del(a[c[t].p]);  
        add(c[t].x);    
    }   
    swap(c[t].x, a[c[t].p]);   
}
int getans() 
{
    int i,j; 
    for(i=1;mex[i]>0;++i);   
    return i;  
}
int main() 
{ 
    int i,j,l=2,r=1;   
    // setIO("input");   
    scanf("%d%d",&n,&m);   
    B=pow(n,0.6666);   
    for(i=1;i<=n;++i) 
    {
        scanf("%d",&a[i]); 
        A[++tot]=a[i];   
    }
    for(i=1;i<=m;++i) 
    {
        int op,a,b;  
        scanf("%d%d%d",&op,&a,&b);  
        if(op==1) 
        {
            ++qcnt;   
            q[qcnt]=query(a,b);  
            q[qcnt].id=qcnt; 
            q[qcnt].t=opcnt;   
        }
        else 
        {
            ++opcnt;   
            c[opcnt]=change(a,b);  
            A[++tot]=b;   
        }
    }
    sort(A+1,A+1+tot);   
    for(i=1;i<=n;++i) a[i]=lower_bound(A+1,A+1+tot,a[i])-A;   
    for(i=1;i<=opcnt;++i) c[i].x=lower_bound(A+1,A+1+tot,c[i].x)-A;   
    sort(q+1,q+1+qcnt);  
    for(i=1;i<=qcnt;++i) 
    {
        for(;l>q[i].l;) add(a[--l]);   
        for(;r<q[i].r;) add(a[++r]);   
        for(;l<q[i].l;) del(a[l++]);   
        for(;r>q[i].r;) del(a[r--]);   
        for(;now<q[i].t;) update(i, ++now);   
        for(;now>q[i].t;) update(i, now--);   
        output[q[i].id]=getans();  
    }
    for(i=1;i<=qcnt;++i) printf("%d\n",output[i]);   
    return 0;   
}

CF682D 【Alyona and Strings】

因为看到“顺序相同”,我们联想到了LCS(最长公共子序列),只不过LCS是对于每个选出来的字符相等且顺序相同,而这里是每个选出来的子串。所以我们按照lcs的套路做,设f[i][j][k][0/1]为:匹配到s的第i位,t的第j位,已经到了第k个字符串,第k个字符串是否继续拓展。

如果s[i]==t[j] 那么f[i][j][k][0]最优决策肯定是f[i-1][j-1][k-1][1]+1或者f[i-1][j-1][k][0]+1(自己新建一段或者拓展之前的一段)

否则,f[i][j][k][1]的最优决策,只能从f[i-1][j]或者f[i][j-1]转移而来,这样就完事了????

Fake!!!如果我s[i]==t[j]的时候,也就是说我这一段是可以选择拓展和不拓展的,如果可以拓展,那不拓展的答案也该有max(f[i-1][j-1][k-1],f[i-1][j-1][k][0])+1,其实就是f[i][j][k][0],对吧

整理一下:

如果s[i]==t[j]:f[i][j][k][0]=max(f[i-1][j-1][k-1][1],f[i-1][j-1][k][0])+1;

f[i][j][k][1]=max(f[i-1][j][k-1][1],f[i][j-1][k-1][1],f[i][j][k][0]);

上代码:

#include<bits/stdc++.h>
using namespace std;
int n,m,k,f[1001][1001][11][2];
char s[1001],t[1001];
int main(){
	int i,j,l;
	cin>>n>>m>>k;
	scanf("%s",s+1),scanf("%s",t+1); 
	for(i=1;i<=n;i++){
		for(j=1;j<=m;j++){
			if(s[i]==t[j]){
				for(l=1;l<=k;l++){
					f[i][j][l][0]=max(f[i-1][j-1][l][0],f[i-1][j-1][l-1][1])+1;
				}
			}
			for(l=1;l<=k;l++)f[i][j][l][1]=max(f[i-1][j][l][1],max(f[i][j-1][l][1],f[i][j][l][0]));	
		}
	}
	cout<<f[n][m][k][1]<<endl;
	return 0;
}

CF438D 【The Child and Sequence】

大家好,我非常喜欢暴力数据结构,于是我用分块A了这题。

首先,想必你来看题解,是因为这道题的取模操作吧!
一个数不停被取模,肯定取不了几次就变成很小的数了(同一道区间开方的题目),而且如果区间最大值小于模数,那取模就没有意义了,所以我们分块,取模暴力,当然还得记录区间最大值,判断此操作是否有必要执行。(还得开O2才能过哦)

具体见代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int n,m;
int a[1000001];
int l[1005],r[1005],bel[1000005];
ll sum[1005];
int mx[1005];
inline ll query(int L,int R){
	int i;
	ll ans=0;
	if(bel[L]==bel[R]){
		for(i=L;i<=R;++i)ans+=a[i];
	}else{
		for(i=L;i<=r[bel[L]];++i)ans+=a[i];
		for(i=l[bel[R]];i<=R;++i)ans+=a[i]; 
		for(i=bel[L]+1;i<=bel[R]-1;++i)ans+=sum[i]; 
	}
	return ans;
}
inline void change(int x,int v){
	sum[bel[x]]-=a[x],sum[bel[x]]+=v;
	a[x]=v;
	int i;
	mx[bel[x]]=0;
	for(i=l[bel[x]];i<=r[bel[x]];++i)mx[bel[x]]=max(mx[bel[x]],a[i]);
}
inline void reset(int L,int R,int p){
	for(int i=L;i<=R;i++)a[i]%=p;
	mx[bel[L]]=0,sum[bel[L]]=0;
	for(int i=l[bel[L]];i<=r[bel[L]];i++)mx[bel[L]]=max(mx[bel[L]],a[i]),sum[bel[L]]+=a[i];
}
inline void modifly(int L,int R,int p){
	int i;
	if(bel[L]==bel[R]){
		reset(L,R,p);
	}else{
		reset(L,r[bel[L]],p);
		reset(l[bel[R]],R,p); 
		for(i=bel[L]+1;i<=bel[R]-1;++i){
			if(mx[i]>=p)
			reset(l[i],r[i],p);
		}
	}
}
int main(){;
	register int i,j;
	scanf("%d%d",&n,&m);
	for(i=1;i<=n;++i)scanf("%d",&a[i]);
	int cnt=sqrt(n);
	for(i=1;i<=cnt;i++){
		l[i]=r[i-1]+1;
		r[i]=l[i]+cnt-1;
	}
	if(r[cnt]<n){
		cnt++;
		l[cnt]=r[cnt-1]+1;
		r[cnt]=n;
	}
	for(i=1;i<=cnt;++i){
		for(j=l[i];j<=r[i];++j){
			bel[j]=i;
			sum[i]+=a[j];
			mx[i]=max(mx[i],a[j]);
		}
	}
	while(m--){
		int opt;
		scanf("%d",&opt);
		if(opt==1){//query
			int l,r;
			scanf("%d%d",&l,&r);
			printf("%lld\n",query(l,r));
		}
		if(opt==2){//modifly
			int l,r,p;
			scanf("%d%d%d",&l,&r,&p);
			modifly(l,r,p);
		}
		if(opt==3){//change
			int x,v;
			scanf("%d%d",&x,&v);
			change(x,v);
		}
	}
	return 0;
}

CF545C 【Woodcutters】

这道题目首先看到数据范围就知道是贪心
显然第一棵树往左倒,第n棵往右倒,中间的树怎么办呢?
考虑到实现简单,我们先按照x坐标从小到大排序,中间的树优先往左倒,倒不了就往右倒,然后记录一个f,表示这棵树是往哪个方向倒,到下一棵树就知道上一棵树的f了,如果这棵树往左倒,那么上一棵树倒下后树根的x坐标必须小于我的坐标-我的长度。如果往右倒,由于不知道下一棵树往哪个方向倒,所以贪心满足自己,直接向右倒就行了

#include<bits/stdc++.h>
using namespace std;
struct node{
	int x,h,f;
}a[100001];
int n;
int cmp(node x,node y){
	return x.x<y.x;
}
int main(){
	int i;
	cin>>n;
	for(i=1;i<=n;i++)scanf("%d%d",&a[i].x,&a[i].h);
	sort(a+1,a+n+1,cmp);
	int ans=2;
	for(i=2;i<n;i++){
		if(a[i].x-a[i].h>a[i-1].x+max(a[i-1].f,0)*a[i-1].h)a[i].f=-1,ans++;
		else if(a[i].x+a[i].h<a[i+1].x)a[i].f=1,ans++;
	}
	if(n==1)cout<<1<<endl;
	else cout<<ans<<endl;
	return 0;
}

P3502 【[POI2010]CHO-Hamsters】

这道题目很妙,当时只有@wlzhouzhuan 巨佬一个做出来。

现在想要是那个时候A这道题该有多爽啊(好吧2010年我才3岁。。。

首先看到题面,我第一想法是dp不可做,为何?我想:有可能不是所有的串首尾相接呀,可能一个串含在另外两个合并起来的大串里呀!。

其实不是的,比如说aaab abc bcc
答案是6

既然都是首尾相接,那就好办了,我们用string_hash求出任意两个字符串要连接起来的最少长度(记为dis[i][j]),用这个跑dp就行了

dp式子为:f[i][j]:串中已经有了i个字符串,最后一个为j的最小长度。
f[i][j]=min(f[i][j],f[i-1][k]+dis[k][j]);

到这里就有60分了(够了够了),可是与AK的bmh201708巨佬差距还是不小。

所以我们要进行优化,算法的瓶颈在m上。

但是您有没有发现,这个式子相当于每次只转移一条边的floyd???

没错哦,先改写成floyd的式子:f[i][j]=min(f[i][j],f[i][k]+f[k][j])

机房里大多数人放弃在这里,她们想不到了。其实这就相当于把字符串看成点,只经过k条边的最短路,这有个东西叫倍增floyd,请读者自行百度

然后还要连一个超级源,不然不知到从哪个串开始,枚举浪费时间。对于超级源来说,出边连的是对应点的字符串长度,入边连inf(相当于不能走,为何不能走?因为走一步就相当于浪费了一条边(走到了空点上),可是必须得走满呀!!!)

还有一个细节:m在矩乘时要减一,因为快速幂res的“1”不知道(是邻接矩阵吗?不是,应为邻接矩阵自己对自己连边是不花费的,可是这里一个点重复两次要花费呀!!!所以“1”就变成底数,然后m--,保证是m次方)

详细实现见代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;//自然溢出法
const int N=1005;//不给数据范围。。。
const ll d=1003;
const ll inf=1e15;//不要太小,其实1e15就够了
int n; 
ll m;
char s[N][10001];//不给数据范围。。。
ll dis[N][N];
ull po[10001];
ull hasa[10001],hasb[10001];
struct ju{
	ll a[N][N];
};
ju mul(ju a,ju b){//矩阵乘法,见倍增floyd
	ju x;
	int i,j,k;
	for(i=0;i<=n;i++)for(j=0;j<=n;j++)x.a[i][j]=inf;
	for(k=0;k<=n;k++){//做floyd
		for(i=0;i<=n;i++){
			for(j=0;j<=n;j++){
				x.a[i][j]=min(x.a[i][j],a.a[i][k]+b.a[k][j]);
			}
		}
	}
	return x;
}
int len[N];
ull chsuc(int x,int p){//后缀哈希值
	return hasa[len[x]]-hasa[p-1]*po[len[x]-p+1]; 
}
ll did(int x,int y,int same){//算dis
	int i,j;
	memset(hasa,0,sizeof(hasa));
	memset(hasb,0,sizeof(hasb));
	for(i=1;i<=len[x];i++)hasa[i]=hasa[i-1]*d+s[x][i];
	for(i=1;i<=len[y];i++)hasb[i]=hasb[i-1]*d+s[y][i];
	for(i=len[y]-same;i>=1;i--){//注意,当两串相同时不可以一个当两个用,最多匹配到倒数第二位
		if(len[x]<i)continue;//i枚举的是b的前缀与a的后缀的重复字符数,显然不能超过len[x]
		ull h1=hasb[i],h2=chsuc(x,len[x]-i+1); 
		if(h1==h2){
			return len[y]-i;
		}
	}
	return len[y];
} 
int main(){
	cin>>n>>m;
	int i,j;
	for(i=1;i<=n;i++)scanf("%s",s[i]+1),len[i]=strlen(s[i]+1);
	po[0]=1;
	for(i=1;i<=10000;i++)po[i]=po[i-1]*d;//奇怪吗?多写string_hash
	for(i=0;i<=n;i++){//超级源为0点
		for(j=0;j<=n;j++){
			if(j==0)dis[i][j]=inf;
			else if(i==0)dis[i][j]=len[j];//build a super start 
			else dis[i][j]=did(i,j,i==j);
		}
	}
	ju hhh,res;
	for(i=0;i<=n;i++)for(j=0;j<=n;j++)hhh.a[i][j]=dis[i][j];	
	res=hhh;
    m--;//m可以为0哦!
	if(m==-1){
		puts("0");
		return 0;
	}
	while(m){//快速幂
		if(m&1)res=mul(res,hhh);
		hhh=mul(hhh,hhh);
		m>>=1;
	}
	ll ans=inf;
	for(i=1;i<=n;i++)ans=min(ans,res.a[0][i]);
	cout<<ans<<endl;
	return 0;
}

CF888E 【Maximum Subsequences】

这道题看到35的数据量,条件反射出meet in the middle(折半枚举)

所谓meet in the middle,就是将原集合分为两个大小最多相差1的子集,

在其中一个子集中2^17暴力枚举,然后对于所有枚举的数,在另一个子集

中查找。

具体来说,以5个数为例(mod 3)

1 2 3 4 5

前两个的状态:0,1,2,1+2=3 3mod3=0

后3个就可以暴力枚举了:

对于3来说,最好情况显然是凑起来是2,于是在子集1中找到第3个元素2

对于4来说,最好情况显然是凑起来也是2,于是在子集1中找到第3个元素1

对于5来说,最好情况显然是凑起来也是2,于是在子集1中找到第3个元素0

既然可以凑出mod-1,那答案一定是mod-1(当然还有凑不成的,只能用

lower_bound找最接*的代替了)

完毕
code:

#include<bits/stdc++.h>
using namespace std;
int n,mod,a[1000001];
long long sum[1000001];
int main(){
	cin>>n>>mod;
	int i,j;
	for(i=1;i<=n;i++)scanf("%d",&a[i]);
	int m=n/2,k=n-m;
	for(i=0;i<(1<<m);i++){
		for(j=1;j<=m;j++)sum[i]+=((i>>(j-1))&1)*a[m-j+1];
        //枚举状态
		sum[i]%=mod;
	}
	sort(sum,sum+(1<<m));//排序好二分
	long long ans=0;
	for(i=0;i<(1<<k);i++){
		long long s=0;
		for(j=1;j<=k;j++)s+=((i>>(j-1))&1)*a[n-j+1]; 
		s%=mod;
		long long x=(lower_bound(sum,sum+(1<<m),(mod-1-s))-sum); 
		long long mx=0;
        //本人第一次RE了,这是处理边界问题,不要在意细节。。。。。
		if(x>0&&x<m-1)mx=max((sum[x]+s)%mod,max((sum[x-1]+s)%mod,(sum[x+1]+s)%mod));
		if(x<=0)mx=max((sum[x]+s)%mod,(sum[x+1]+s)%mod);
		if(x>=m-1)mx=max((sum[x]+s)%mod,(sum[x-1]+s)%mod);
		ans=max(ans,mx);
	}
	cout<<ans<<endl;
	return 0;
} 
posted @ 2023-07-14 14:37  Anticipator  阅读(37)  评论(0)    收藏  举报