题意:有一堆火柴构成了一个加减法式子,你可以把火柴重新组合,要求数字个数和原来一样多,每个数字的位数和对应原数字位数一样多,总火柴数量也一样多,要求你构造新的式子算出来的结果最大。
思路:设mx[ i ][ j ]为用了 j 根火柴组成了最大的 i 位数,mn[ i ][ j ]为最小值,这两个数组用dp预处理随便搞一搞就行,然后我们求出来原式子的所有火柴为m,数字个数为k,设d[ i ][ j ]为算到了第 i 个数字用了 j 根火柴所得到最大的值,很显然对于d[ i + 1 ][ x ]只有两个转移方程,假设第 i + 1 个数有x位数字,枚举火柴数量 p 所能组成的最大数,用加法: d[ i + 1 ][ j + p + 2] = max(d[ i + 1 ][ j + p + 2] , d[ i ][ j ] + mx[ x ][ p ]),用减法:你来搞定吧。那么这题就结束了。
#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll mn[11][1000],mx[11][1000],inf=1e18;
int a[11]={6,2,5,5,4,5,6,3,7,6};
void init()
{
for(int i=0;i<11;i++)
for(int j=0;j<1000;j++)
mx[i][j]=-1,mn[i][j]=1e18;
mn[0][0]=mx[0][0]=0;
for(int i=0;i<10;i++)
for(int j=0;j<800;j++)
if(mx[i][j]!=-1)
{
for(int k=1;k<=9;k++)
{
mn[i+1][j+a[k]]=min(mn[i+1][j+a[k]],mn[i][j]*10+k);
mx[i+1][j+a[k]]=max(mx[i+1][j+a[k]],mx[i][j]*10+k);
}
int k=0;
if(mn[i][j]!=0)
mn[i+1][j+a[k]]=min(mn[i+1][j+a[k]],mn[i][j]*10+k);
if(mx[i][j]!=0)
mx[i+1][j+a[k]]=max(mx[i+1][j+a[k]],mx[i][j]*10+k);
}
mn[1][6]=0;
}
ll d[101][10001];
int b[101];
int main()
{
init();
int T;
scanf("%d",&T);
while(T--)
{
int n,m=0,k=1;
char s[101];
for(int i=1;i<100;i++)b[i]=0;
scanf("%d%s",&n,s+1);
for(int i=1;i<=n;i++)
{
if(s[i]=='+')m+=2,k++;
else if(s[i]=='-')m++,k++;
else m+=a[s[i]-'0'],b[k]++;
}
for(int i=0;i<=k;i++)
for(int j=0;j<=m;j++)d[i][j]=-1;
d[0][0]=0;
for(int p=1;p<=700;p++)
if(mx[b[1]][p]!=-1)
d[1][p]=mx[b[1]][p];
for(int i=1;i<k;i++)
for(int j=0;j<=m;j++)
if(d[i][j]!=-1)
{
for(int p=1;p<=700;p++)
{
if(mx[b[i+1]][p]!=-1)
d[i+1][j+p+2]=max(d[i+1][j+p+2],d[i][j]+mx[b[i+1]][p]);
if(mn[b[i+1]][p]!=inf)
d[i+1][j+p+1]=max(d[i+1][j+p+1],d[i][j]-mn[b[i+1]][p]);
}
}
printf("%lld\n",d[k][m]);
}
}
update:
// 感谢吉林大学的一位数学大佬网友
思路:
令
则:
令:
令:
我们反演一下:
那么:
切换枚举次序: /
我们线性筛预处理 的前缀和,然后就可用整除分块搞定了。
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1e7+1,mod=1<<30;
int pri[maxn/10],mu[maxn],phi[maxn],vis[maxn],cnt;
int d[maxn];
void add(int &x,ll y)
{
x=x-mod+y;
if(x<0)x+=mod;
if(x>=mod)x-=mod;
}
void add(int &x,int y)
{
x+=y;
if(x>=mod)x-=mod;
}
void init(int n)
{
mu[1]=phi[1]=d[1]=1;
for(int i=2;i<=n;i++)
{
if(!vis[i])
pri[++cnt]=i,mu[i]=-1,phi[i]=i-1,d[i]=i-2;
for(int j=1;j<=cnt&&pri[j]*i<=n;j++)
{
vis[pri[j]*i]=1;
if(i%pri[j]!=0)
{
mu[i*pri[j]]=-mu[i];
phi[i*pri[j]]=phi[i]*phi[pri[j]];
d[i*pri[j]]=(1ll*d[i]*phi[pri[j]]-d[i]+mod)%mod;
}
else
{
phi[i*pri[j]]=phi[i]*pri[j];
int m=i*pri[j],x=1;
while(m%pri[j]==0)
m/=pri[j],x*=pri[j];
d[i*pri[j]]=(1ll*d[m]*phi[x]%mod-1ll*d[m]*phi[x/pri[j]]%mod+mod)%mod;
break;
}
}
}
for(int i=1;i<=n;i++)
{
d[i]=1ll*d[i]*i%mod*i%mod*i%mod;
add(d[i],d[i-1]);
}
}
ll cal(int x)
{
if(x%2)
{
ll res=1ll*(x+1)*(x+1)/4;
if(x%3==1)
return res%mod*x%mod*x%mod*x%mod*((2*x+1)/3)%mod;
if(x%3==2)
return res/3%mod*x%mod*x%mod*x%mod*(2*x+1)%mod;
return 1ll*x/3*x%mod*x%mod*(res%mod)%mod*(2*x+1)%mod;
}
else
{
ll res=1ll*x*x/4;
if(x%3==1)
return res*x%mod*(x+1)%mod*(x+1)%mod*((2*x+1)/3)%mod;
if(x%3==2)
return res*x%mod*((x+1)/3)%mod*(x+1)%mod*(2*x+1)%mod;
return res/3*x%mod*(x+1)%mod*(x+1)%mod*(2*x+1)%mod;
}
}
int main()
{
init(1e7);
int T,n;
scanf("%d",&T);
while(T--)
{
scanf("%d",&n);
int ans=0;
for(int l=1,r;l<=n;l=r+1)
{
r=n/(n/l);
add(ans,(1ll*(d[r]-d[l-1]+mod)*cal(n/l))%mod);
}
printf("%d\n",ans);
}
}
题意:你可以选择一个区间,然后得到一个权值=该区间的和*该区间最小值,求最大权值。
思路:枚举最小值ai,然后用线段树找一个最大的包含 i 的区间[ l , r ],该区间最小值为ai,然后再用最大最小值线段树记录ai的前缀和,我们记maxR为 [ i , r ]的最大前缀和,maxL为[ l -1, i - 1 ]的最大前缀和,minR,minL同理,然后用两颗线段树求出这四个值,如果ai>=0,那么选择ai为最小值所能得到最优的答案是 ai*(maxR - minL),如果ai<0,那么最优答案就是 ai*(minR - maxL),搞定。
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=5e5+10;
int mn[maxn*4],a[maxn],n,cur1,cur2;
void up(int o,int l,int r,int k,int v)
{
if(l==r)
{
mn[o]=v;
return;
}
int m=(l+r)/2,ls=o*2,rs=o*2+1;
if(k<=m)up(ls,l,m,k,v);
else up(rs,m+1,r,k,v);
mn[o]=min(mn[ls],mn[rs]);
}
void qu(int o,int l,int r,int ql,int qr,int v,int tp)
{
int m=(l+r)/2,ls=o*2,rs=o*2+1;
if(l>=ql&&r<=qr)
{
if(l==r)
{
if(mn[o]<v)
{
if(tp==1)
cur1=max(cur1,l);
else
cur2=min(cur2,l);
}
return;
}
if(tp==1)
{
if(mn[rs]<v)
qu(rs,m+1,r,ql,qr,v,tp);
else if(mn[ls]<v)qu(ls,l,m,ql,qr,v,tp);
}
else
{
if(mn[ls]<v)qu(ls,l,m,ql,qr,v,tp);
else if(mn[rs]<v)qu(rs,m+1,r,ql,qr,v,tp);
}
return;
}
if(ql<=m)qu(ls,l,m,ql,qr,v,tp);
if(qr>m)qu(rs,m+1,r,ql,qr,v,tp);
}
ll Mx[maxn*4],Mn[maxn*4];
void update(int o,int l,int r,int k, ll v)
{
if(l==r)
{
Mx[o]=Mn[o]=v;
return;
}
int m=(l+r)/2,ls=o*2,rs=o*2+1;
if(k<=m)update(ls,l,m,k,v);
else update(rs,m+1,r,k,v);
Mx[o]=max(Mx[ls],Mx[rs]);
Mn[o]=min(Mn[ls],Mn[rs]);
}
ll qumax(int o,int l,int r,int ql,int qr)
{
if(l>=ql&&r<=qr)return Mx[o];
int m=(l+r)/2,ls=o*2,rs=o*2+1;
ll res=-1e18;
if(ql<=m)res=max(res,qumax(ls,l,m,ql,qr));
if(qr>m)res=max(res,qumax(rs,m+1,r,ql,qr));
return res;
}
ll qumin(int o,int l,int r,int ql,int qr)
{
if(l>=ql&&r<=qr)return Mn[o];
int m=(l+r)/2,ls=o*2,rs=o*2+1;
ll res=1e18;
if(ql<=m)res=min(res,qumin(ls,l,m,ql,qr));
if(qr>m)res=min(res,qumin(rs,m+1,r,ql,qr));
return res;
}
ll query(int p,int l,int r,int tp)
{
if(tp==1)return qumax(1,0,n,p,r)-qumin(1,0,n,l-1,p-1);
else return qumin(1,0,n,p,r)-qumax(1,0,n,l-1,p-1);
}
ll gao(int p)
{
cur1=0,cur2=n+1;
qu(1,1,n,p,n,a[p],2);
qu(1,1,n,1,p,a[p],1);
cur1++,cur2--;
ll ans=0;
if(a[p]>=0)ans=query(p,cur1,cur2,1)*a[p];
else ans=query(p,cur1,cur2,2)*a[p];
return ans;
}
int main()
{
int x;
ll ans=-1e18,res=0;
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&x);
a[i]=x;
up(1,1,n,i,x);
res+=x;
update(1,0,n,i,res);
}
for(int i=1;i<=n;i++)
ans=max(ans,gao(i));
printf("%lld\n",ans);
}
题意:给一颗树,有m次询问,每次询问 u v w,要求 u 到 v 的路径中有多少边权值<=w。
思路:以1位根建树,然后在树上建可持久化线段树,记录根节点到当前节点经过的边权,每次查询就在rt[ u ]和rt[ v ]中查询[ 0 , w ]的区间和然后加起来,假设他们lca 为fu,那么肯定还要减去rt[ fu ]的[ 0, w ]区间和*2。
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+10,inf=1e9+7;
int rt[maxn],sum[maxn*40],ls[maxn*40],rs[maxn*40],n,cnt;
vector<int>G[maxn],dis[maxn];
int deep[maxn],f[maxn][20];
void up(int pre,int& o,int l,int r,int k)
{
o=++cnt;
ls[o]=ls[pre],rs[o]=rs[pre];
sum[o]=sum[pre]+1;
if(l==r)return;
int m=(1ll*l+r)/2;
if(k<=m)up(ls[pre],ls[o],l,m,k);
else up(rs[pre],rs[o],m+1,r,k);
}
int qu(int o,int l,int r,int k)
{
if(l==r)return sum[o];
int m=(1ll*l+r)/2;
if(k<=m)return qu(ls[o],l,m,k);
else return sum[ls[o]]+qu(rs[o],m+1,r,k);
}
void dfs(int u,int fa,int dep)
{
deep[u]=dep;
f[u][0]=fa;
for(int i=0;i<G[u].size();i++)
{
int v=G[u][i];
if(v==fa)continue;
up(rt[u],rt[v],0,inf,dis[u][i]);
dfs(v,u,dep+1);
}
}
void init()
{
for(int i=1;i<20;i++)
for(int j=1;j<=n;j++)
f[j][i]=f[f[j][i-1]][i-1];
}
int lca(int p,int q)
{
if(deep[p]<deep[q])swap(p,q);
for(int i=19;i>=0;i--)
if(deep[f[p][i]]>=deep[q])
p=f[p][i];
if(p==q)return p;
for(int i=19;i>=0;i--)
if(f[p][i]!=f[q][i])
p=f[p][i],q=f[q][i];
return f[p][0];
}
int main()
{
int m,u,v,w;
scanf("%d%d",&n,&m);
for(int i=1;i<n;i++)
{
scanf("%d%d%d",&u,&v,&w);
G[u].push_back(v);
G[v].push_back(u);
dis[u].push_back(w);
dis[v].push_back(w);
}
dfs(1,0,1);
init();
while(m--)
{
scanf("%d%d%d",&u,&v,&w);
int ans=0;
ans=qu(rt[u],0,inf,w)+qu(rt[v],0,inf,w);
int fu=lca(u,v);
ans-=qu(rt[fu],0,inf,w)*2;
printf("%d\n",ans);
}
}
虽然 K 题也是我写的,但是我觉得这题有点恶心,就不写这题题解了。
7题倒二,这个罚时,我枯了。