考虑朴素的最大子矩阵和
首先枚举上下边界,然后问题就转化成了维护最大子段和
这个问题可以用线段树解决
用 m a x v maxv maxv、 p r e v prev prev、 s u f v sufv sufv分别表示当前区间的最大值、当前区间紧贴左端点的最大值、当前区间紧贴右端点的最大值
单点修改时间复杂度位 O ( l o g N ) O(logN) O(logN),查询复杂度为 O ( 1 ) O(1) O(1)
因为总共只有 O ( N ) O(N) O(N)个点,所以时间复杂度是 O ( N 2 + N l o g N ) O(N^2+NlogN) O(N2+NlogN)
#include
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fd(i,a,b) for(i=a;i>=b;i--)
#define MOD 1000000007
#define N 2005
#define ll long long
using namespace std;
ll sum[N*4],maxv[N*4],Prev[N*4],sufv[N*4];
void pushup(int rt)
{
sum[rt] = sum[rt<<1] + sum[rt<<1|1];
maxv[rt] = max(maxv[rt<<1],maxv[rt<<1|1]);
maxv[rt] = max(maxv[rt],sufv[rt<<1]+Prev[rt<<1|1]);
Prev[rt] = max(Prev[rt<<1],sum[rt<<1]+Prev[rt<<1|1]);
sufv[rt] = max(sufv[rt<<1|1],sum[rt<<1|1]+sufv[rt<<1]);
}
void build(int rt,int l,int r)
{
if (l == r) {maxv[rt] = Prev[rt] = sufv[rt] = sum[rt] = 0; return;}
int mid = (l + r) >> 1;
build(rt<<1,l,mid); build(rt<<1|1,mid+1,r);
pushup(rt);
}
void update(int rt,int l,int r,int pos,ll v)
{
if (l == r) {sum[rt] += v; maxv[rt] = Prev[rt] = sufv[rt] = max(0ll,sum[rt]); return;}
int mid = (l + r) >> 1;
if (pos <= mid) update(rt<<1,l,mid,pos,v); else update(rt<<1|1,mid+1,r,pos,v);
pushup(rt);
}
ll query() {return maxv[1];}
struct node
{
int x,y,c;
bool operator < (const node &rhs) const
{
return x<rhs.x || (x==rhs.x && y<rhs.y);
}
}p[N];
int main()
{
int t,n;
scanf("%d",&t);
while (t--)
{
vector <int> keyx;
vector <int> keyy;
scanf("%d",&n);
for (int i=0;i<n;i++)
{
scanf("%d%d%d",&p[i].x,&p[i].y,&p[i].c);
keyx.push_back(p[i].x);
keyy.push_back(p[i].y);
}
sort(keyx.begin(),keyx.end());
sort(keyy.begin(),keyy.end());
int pp=unique(keyx.begin(),keyx.end())-keyx.begin();
int qq=unique(keyy.begin(),keyy.end())-keyy.begin();
for (int i=0;i<n;i++)
{
p[i].x=lower_bound(keyx.begin(),keyx.begin()+pp,p[i].x)-keyx.begin()+1;
p[i].y=lower_bound(keyy.begin(),keyy.begin()+qq,p[i].y)-keyy.begin()+1;
}
sort(p,p+n);
ll ans=0;
for (int i=0;i<pp;i++)
{
int x=i+1;
int tmp=0;
while (tmp<n && p[tmp].x<x) tmp++;
build(1,1,qq);
while (tmp<n)
{
int curx=p[tmp].x;
while (tmp<n && p[tmp].x==curx)
{
update(1,1,qq,p[tmp].y,p[tmp].c);
tmp++;
}
ans=max(ans,query());
}
}
printf("%lld\n",ans);
}
return 0;
}
根据绝对值的定义,我们可以把这个平面分成 O ( N 2 ) O(N^2) O(N2)个区域,并且在区域内的点计算答案的时候都可以把绝对值拆掉
因为 l c m ( 2 , 3 , 4 , 5 ) = 60 lcm(2,3,4,5)=60 lcm(2,3,4,5)=60,所以一个区域内的点一定是以 60 ∗ 60 60*60 60∗60作为循环节
所以对每个区域,首先暴力做出 60 ∗ 60 60*60 60∗60的情况,然后把边边角角的算上即可
时间复杂度是 O ( 6 0 2 N 2 ) O(60^2N^2) O(602N2)
#include
#include
#define fo(i,a,b) for(i=a;i<=b;i++)
#define N 20
using namespace std;
struct www{int x,y,k,t;} v[N];
int n,m,numx,numy,num,T,i,j,k1,k2,x1,y1,x2,y2;
long long res;
int f[100][100],h[100],w[100];
bool cmp1(const www &a,const www &b) {return a.x < b.x;}
bool cmp2(const www &a,const www &b) {return a.y < b.y;}
bool check(int x,int y)
{
int i;
fo(i,1,n) if ((abs(x-v[i].x)+abs(y-v[i].y))%v[i].k != v[i].t) return false;
return true;
}
void add(int x1,int y1,int x2,int y2)
{
int i,j,x,y;
fo(i,1,60)
fo(j,1,60)
{
x = x1 + i - 1; y = y1 + j - 1;
if (check(x,y)) f[i][j] = 1; else f[i][j] = 0;
}
numx = (x2-x1+1) / 60; numy = (y2-y1+1) / 60;
num = 0; fo(i,1,60) fo(j,1,60) num += f[i][j];
res += 1ll * numx * numy * num;
num = 0; fo(i,1,(x2-x1+1) % 60) fo(j,1,60) num += f[i][j];
res += 1ll * numy * num;
num = 0; fo(i,1,60) fo(j,1,(y2-y1+1) % 60) num += f[i][j];
res += 1ll * numx * num;
num = 0; fo(i,1,(x2-x1+1)%60) fo(j,1,(y2-y1+1)%60) num += f[i][j];
res += 1ll * num;
return;
}
int main()
{
scanf("%d",&T);
while (T--)
{
scanf("%d%d",&n,&m);
fo(i,1,n) scanf("%d%d%d%d",&v[i].x,&v[i].y,&v[i].k,&v[i].t);
//res = 0; add(0,0,m,m); cout<
sort(v+1,v+n+1,cmp1);
k1 = 1; h[1] = v[1].x; fo(i,2,n) if (v[i].x != v[i-1].x) h[++k1] = v[i].x;
sort(v+1,v+n+1,cmp2);
k2 = 1; w[1] = v[1].y; fo(i,2,n) if (v[i].y != v[i-1].y) w[++k2] = v[i].y;
res = 0;
fo(i,0,k1)
{
x1 = h[i]; x2 = h[i+1]-1; if (i == k1) x2 = m;
fo(j,0,k2)
{
y1 = w[j]; y2 = w[j+1]-1; if (j == k2) y2 = m;
add(x1,y1,x2,y2);
//cout<
}
}
cout<<res<<endl;
}
return 0;
}
枚举 f ( n , m ) − n f(n,m)-n f(n,m)−n的值,因为这个值不可能很大
然后反向推出 n n n,check一下是否是合法答案
首先一个非常显然的结论就是不可能有很多 ′ ? ′ '?' ′?′
其实只要暴力做最后50~100个 ′ ? ′ '?' ′?′就好了,前面的 ′ ? ′ '?' ′?′全部可以填0
当然你也可以在DP大于1e18的时候直接break
这种题的经典做法就是考虑每一位填某个数之后的后继所有方案数
比如假设这一位填 3 3 3的时候有 x x x种方案,如果x比k小就填3,否则用k减去x,然后继续枚举456789
首先把所有非 ′ ? ′ '?' ′?′的数的贡献算出来,然后扔掉
f [ i ] [ j ] [ k ] f[i][j][k] f[i][j][k]表示填到第 i i i个 ′ ? ′ '?' ′?′,当前的模为 j j j,并且这一位填 k k k的方案数
然后从低位往高位转移就好
填的时从高位往地位确定
还有就是小心爆longlong
#include
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fd(i,a,b) for(i=a;i>=b;i--)
#define MOD 1000000007
#define N 50005
using namespace std;
int T,n,m,q,i,j,num,flag,k1,k2;
char ch[N];
int a[N];
__int128_t ans[N],e[N],f[N][25][15];
__int128_t res,s,limit;
long long ress,k,rest_init,rest;
void init()
{
res = 0;
fo(i,1,n) if (a[i] == -1) res = (res * 10) % MOD; else res = (res * 10 + a[i]) % MOD;
ans[n] = 1; fd(i,n-1,1) ans[i] = (ans[i+1] * 10) % MOD;
s = 1; fd(i,n,1) {e[i] = s; s = (s * 10) % m;}
fo(i,1,n+1) fo(j,0,m-1) fo(k1,0,9) f[i][j][k1] = 0;
rest = 0;
fo(i,1,n) if (a[i] > 0) rest = (rest + a[i] * e[i]) % m;
rest_init = rest;
}
int main()
{
//freopen("1.in","r",stdin);
scanf("%d",&T);
while (T--)
{
scanf("%d%d%d",&n,&m,&q);
scanf("%s",ch);
fo(i,1,n) if (ch[i-1] == '?') a[i] = -1; else a[i] = ch[i-1] - '0';
//num = 0; fo(i,1,n) if (a[i] == -1) num++;
//fo(i,1,n) if (a[i] == -1) if (num > 100) a[i] = 0; else break;
init();
num = 0;
fo(i,1,n)
if (a[i] == -1)
{
num++;
ans[num] = ans[i];
e[num] = e[i];
}
n = num;
flag = 0;
f[n+1][0][0] = 1;
fd(i,n,1)
{
fo(j,0,m-1)
fo(k1,0,9)
fo(k2,0,9)
f[i][(j+e[i]*k1)%m][k1] = f[i][(j+e[i]*k1)%m][k1] + f[i+1][j][k2];
fo(j,0,m-1) fo(k1,0,9) if (f[i][j][k1] > (__int128_t)1e18) {flag = 1; break;}
if (flag) break;
}
if (flag == 0) flag = 1; else flag = i;
while (q--)
{
scanf("%lld",&k);
rest = rest_init;
limit = 0;
fo(j,0,9) limit += f[flag][(m-rest)%m][j];
if ((__int128_t)k > limit) {printf("-1\n"); continue;}
long long ress = res;
fo(i,flag,num)
{
fo(j,0,9)
{
if ((__int128_t)k > f[i][(m-rest)%m][j]) k -= f[i][(m-rest)%m][j]; else break;
}
ress = (ress + j * ans[i]) % MOD;
rest = (rest + j * e[i]) % m;
}
printf("%lld\n",ress);
}
}
return 0;
}
一个显然的贪心是每次暴力取最大的
所以用一个优先队列维护所有的叶子结点,如果取走了最后一个叶子结点就把父亲节点加入优先队列
#include
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
struct node
{
int a,b;
bool operator < (const node &rhs) const
{
return b<rhs.b;
}
};
int T,n,i,op,x;
int a[205000];
long long sum[5];
int main()
{
scanf("%d",&T);
while (T--)
{
scanf("%d",&n);
fo(i,1,n) a[i] = a[i*2] = a[i*2+1] = -1;
fo(i,1,n) scanf("%d",&a[i]);
priority_queue<node> q;
fo(i,1,n)
if (a[i*2] == -1 && a[i*2+1] == -1)
q.push(node{i,a[i]});
sum[0] = sum[1] = 0;
op = 0;
while (!q.empty())
{
x = q.top().a; q.pop();
sum[op] = sum[op] + a[x]; a[x] = -1;
x = x / 2;
if (a[x*2] == -1 && a[x*2+1] == -1) q.push({x,a[x]});
op ^= 1;
}
cout<<sum[0]<<" "<<sum[1]<<endl;
}
return 0;
}