初一上 合集
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;
}

浙公网安备 33010602011771号