传送门:CF1107
有一种CF越来越板/水的感觉,完全是手速场(不看题解就水完了啊
前缀和后 c h e c k check check的复杂度可以做到 O ( ( n x ) 2 ) O((\dfrac{n}{x})^2) O((xn)2)。
称矩阵是 k k k可行的当且仅当 x = k x=k x=k时满足题中条件。
若矩阵同时是 a , b a,b a,b可行的,且 g c d ( a , b ) = 1 gcd(a,b)=1 gcd(a,b)=1,则矩阵也是 a ⋅ b a·b a⋅b可行的。
分解 n n n的质因子后枚举每个质因子的幂次即可。
复杂度玄学能过。
#include
typedef long long ll;
using namespace std;
const int N=5205;
int n,s[N][N],p[N],num,ans;
char cp;
inline int rd(int &x)
{
cp=getchar();x=0;
for(;!isdigit(cp);cp=getchar());
for(;isdigit(cp);cp=getchar()) x=x*10+(cp^48);
}
inline void ins(int x,int y)
{
int i,msk;
for(cp=getchar();;cp=getchar()){
if(cp>='0' && cp<='9'){
msk=cp-'0';break;
}
if(cp>='A' && cp<='F'){
msk=10+cp-'A';
break;
}
}
for(i=0;i<4;++i) s[x][y+i]=(msk>>(3-i)&1);
}
inline bool ck(int x)
{
int i,j,k,bs=x*x;
for(i=x;i<=n;i+=x)
for(j=x;j<=n;j+=x){
k=s[i][j]-s[i-x][j]-s[i][j-x]+s[i-x][j-x];
if((k!=bs)&&(k!=0)) return false;
}
return true;
}
int main(){
int i,j,k;
scanf("%d",&n);
for(i=1;i<=n;++i)
for(j=1;j<=n;j+=4) ins(i,j);
for(i=1;i<=n;++i)
for(j=1;j<=n;++j)
s[i][j]+=s[i-1][j]+s[i][j-1]-s[i-1][j-1];
k=n;
for(i=2;i*i<=k;++i) if(!(k%i)){
p[++num]=i;for(;!(k%i);k/=i);
}
if(k>1) p[++num]=k;ans=1;
for(i=1;i<=num;++i){
for(k=p[i],j=k;!(n%j);j*=k){
if(!ck(j)) break;
}
ans*=(j/k);
}
printf("%d",ans);
return 0;
}
先完全背包求出 C i C_i Ci表示删除连续一段 0 / 1 0/1 0/1的最大花费。
d p [ l ] [ r ] [ 0 / 1 ] [ l e n ] dp[l][r][0/1][len] dp[l][r][0/1][len]表示将区间 [ l , r ] [l,r] [l,r]删除到只剩下 l e n len len个 0 / 1 0/1 0/1的最大代价, g [ l ] [ r ] g[l][r] g[l][r]表示将区间 [ l , r ] [l,r] [l,r]全部删除的最大代价。
d p [ l ] [ r ] [ S r ] [ l e n ] = m a x ( d p [ l ] [ k ] [ S r ] [ l e n − 1 ] + g [ k + 1 ] [ r − 1 ] ) dp[l][r][S_r][len]=max(dp[l][k][S_r][len-1]+g[k+1][r-1]) dp[l][r][Sr][len]=max(dp[l][k][Sr][len−1]+g[k+1][r−1])
g [ l ] [ r ] = m a x ( d p [ l ] [ k ] [ 0 / 1 ] [ l e n ] + C l e n + g [ k + 1 ] [ r ] ) g[l][r]=max(dp[l][k][0/1][len]+C_{len}+g[k+1][r]) g[l][r]=max(dp[l][k][0/1][len]+Clen+g[k+1][r])
复杂度 O ( n 4 ) O(n^4) O(n4)
#include
typedef long long ll;
using namespace std;
const int N=105;
int n,a[N];
char s[N];
ll dp[N][N][2][N],g[N][N],nf,cn[2],c[N];
inline void upp(ll &x,ll y){if(y>x) x=y;}
int main(){
int i,j,k,l,r;
memset(dp,-0x3f,sizeof(dp));
memset(g,-0x3f,sizeof(g));nf=g[0][0];
scanf("%d%s",&n,s+1);
for(i=1;i<=n;++i) scanf("%d",&a[i]),s[i]-='0';
for(i=1;i<=n;++i)
for(j=n;j>=i;--j)
c[j]=max(c[j],c[j-i]+a[i]);
for(i=1;i<=n;++i) dp[i][i][s[i]][1]=0,g[i+1][i]=0;g[1][0]=0;
for(l=n;l;--l)
for(r=l;r<=n;++r)
for(i=1;l+i-1<=r;++i){
for(k=0;k<2;++k) cn[k]=dp[l][r][k][i];
for(k=r+1;k<=n;++k) if(cn[s[k]]>nf)
upp(dp[l][k][s[k]][i+1],cn[s[k]]+g[r+1][k-1]);
for(k=r;k<=n;++k)
upp(g[l][k],max(cn[0],cn[1])+c[i]+g[r+1][k]);
}
printf("%I64d",g[1][n]);
return 0;
}
构造二分图,左部 n n n个点分别表示 n n n个贷,右部 n n n个点中第 i i i个点表示倒数第 i i i个月借贷。 g ( i , j ) = m a x ( 0 , a i − m i n ( j − 1 , k i ) × b i ) g(i,j)=max(0,a_i-min(j-1,k_i)\times b_i) g(i,j)=max(0,ai−min(j−1,ki)×bi)
那么就是裸的二分图最大权匹配。费用流会T,跑个KM即可。
#include
typedef long long ll;
using namespace std;
const int N=505;
const ll inf=1e18;
int n,tim,vs[2][N],bel[N];
ll g[N][N],tg[2][N],slack[N],ans;
inline void upp(ll &x,ll y){if(y>x) x=y;}
inline void dnn(ll &x,ll y){if(y<x) x=y;}
bool dfs(int x)
{
int i;ll v;vs[0][x]=tim;
for(i=1;i<=n;++i) if(vs[1][i]!=tim){
v=tg[0][x]+tg[1][i]-g[x][i];
if(v==0){
vs[1][i]=tim;
if((!bel[i])||dfs(bel[i])){
bel[i]=x;return true;
}
}else dnn(slack[i],v);
}
return false;
}
int main(){
int i,j,x,y,z;ll d;
scanf("%d",&n);
for(i=1;i<=n;++i){
scanf("%d%d%d",&x,&y,&z);
for(j=1;j<=n;++j) g[i][j]=max(0LL,x-(ll)y*min(j-1,z));
}
for(i=1;i<=n;++i){
for(d=0LL,j=1;j<=n;++j)
upp(d,g[i][j]);
tg[0][i]=d;
}
for(x=1;x<=n;++x){
memset(slack,0x7f,sizeof(slack));
for(;;){
tim++;if(dfs(x)) break;d=inf;
for(i=1;i<=n;++i) if(vs[1][i]!=tim) dnn(d,slack[i]);
for(i=1;i<=n;++i) if(vs[0][i]==tim) tg[0][i]-=d;
for(i=1;i<=n;++i) vs[1][i]==tim?tg[1][i]+=d:slack[i]-=d;
}
}
for(i=1;i<=n;++i) ans+=g[bel[i]][i];
printf("%I64d",ans);
return 0;
}
设 e i = d i − d i − 1 e_i=d_i-d_{i-1} ei=di−di−1。单调栈对于求出 [ l i , r i ] [l_i,r_i] [li,ri]表示 m a x j = l i r i e j = e i max_{j=l_i}^{r_i} e_j=e_i maxj=liriej=ei的最大区间。
再线段树维护一下区间前缀和后缀和最大值即可。
#include
typedef long long ll;
using namespace std;
const int N=3e5+10;
int n,a,lf[N],rt[N];
ll v[N],prf[N],suf[N],ans;
int c[N],d[N],stk[N],top;
char cp;
inline void rd(int &x)
{
cp=getchar();x=0;
for(;!isdigit(cp);cp=getchar());
for(;isdigit(cp);cp=getchar()) x=x*10+(cp^48);
}
#define lc k<<1
#define rc k<<1|1
#define mid (l+r>>1)
#define pu(k) mx[k]=max(mx[lc],mx[rc])
struct seg{
ll mx[N<<2];
void build(int k,int l,int r)
{
if(l==r) {mx[k]=v[l];return;}
build(lc,l,mid);build(rc,mid+1,r);
pu(k);
}
ll ask(int k,int l,int r,int L,int R)
{
if(L<=l && r<=R) return mx[k];
if(R<=mid) return ask(lc,l,mid,L,R);
if(L>mid) return ask(rc,mid+1,r,L,R);
return max(ask(lc,l,mid,L,R),ask(rc,mid+1,r,L,R));
}
}qz,hz;
int main(){
int i,j,k,pre=0;ll res;
rd(n);rd(a);v[0]=v[n+1]=0LL;
for(i=1;i<=n;++i){
rd(d[i]);rd(c[i]);k=d[i];d[i]-=pre;pre=k;
c[i]=a-c[i];v[i]=v[i-1]+c[i];ans=max(ans,(ll)c[i]);
}
qz.build(1,1,n);memcpy(prf,v,sizeof(prf));
for(i=n;i;--i) v[i]=v[i+1]+c[i];
hz.build(1,1,n);memcpy(suf,v,sizeof(suf));
stk[0]=1;
for(i=2;i<=n;++i){
for(;top && d[stk[top]]<=d[i];--top)
rt[stk[top]]=i-1;
lf[i]=stk[top];stk[++top]=i;
}
for(;top;--top) rt[stk[top]]=n;
for(i=2;i<=n;++i){
res=max(0LL,qz.ask(1,1,n,i,rt[i])-prf[i-1]);
res+=hz.ask(1,1,n,lf[i],i-1)-suf[i];
ans=max(ans,res-(ll)d[i]*d[i]);
}
printf("%I64d",ans);
return 0;
}