第四场 hdu 多校
1007
我们很容易想到用 a a a 一遍一遍的模拟,或者用大的 b b b 去更新 $ a$, 然后让 a a a 有一个时间的限制。但是两种方法在最坏下都会到 O ( n 2 ) O(n^2) O(n2) ,显然会TLE 。
我们不妨去把两者结合一下看一看,前面小的数用 a a a 自己更新,后面 大的 用 b b b 去更新,肯定会比原来的更优,因为 小的数必然会被更新,大的数更新的少,而 b b b 中大的数必然会去更新 a a a 中小的数。但是到这里我们还要看 我们设置的中介点是什么。对于第一种操作,我们假设我们设置的为b 中的第 x x x 大,我们设比当前小的数有 i 个,那么显然其 i i i 的个数期望为 O ( ∑ ( 1 − x n ) i = n x ) O(\sum(1-\frac{x}{n})^i = \frac{n}{x}) O(∑(1−nx)i=xn) 由无穷级数得到的。 表示有 1 个比他小的期望,2个,3个… 之和。
所以总的复杂度为 n 2 x + n x \frac{n^2}{x} + nx xn2+nx 然后运用不等式可得 x x x 取得 n \sqrt n n 即可。
#include<bits/stdc++.h>
using namespace std;
#define int long long
int n,q;
#define endl '\n'
// 手动取模要不然会tle
inline int fix(int x)
{
while(x<0)
x+=n;
while(x>=n)
x-=n;
return x;
}
void solve()
{
cin>>n>>q;
vector<int> a(n);
vector<int> inib(n);
int ans = 0;
for(int i=0;i<n;i++)
{ cin>>a[i];
ans += a[i];
}
vector<pair<int,int> > b(n);
for(int i=0;i<n;i++)
{ cin>>b[i].first;
inib[i] = b[i].first;
b[i].second = i;
}
sort(b.begin(),b.end(),greater<pair<int,int> > ());
int lim = sqrt(n);
lim = min(lim,n-1);
vector<int > les;
//cout<<lim<<endl;
for(int i=0;i<n;i++)
{
if(a[i] <= b[lim].first)
les.push_back(i);
}
int len = les.size();
while(q--)
{ int k;
cin>>k;
for(int i=0;i<=lim;i++)
{
if(a[fix(b[i].second - k)] < b[i].first)
{
ans += b[i].first - a[fix(b[i].second - k)];
a[fix(b[i].second - k) ] = b[i].first;
}
}
for(int i=0;i<len;i++)
{ int pos = les[i];
if(a[pos] < inib[fix(pos + k)])
{
ans += inib[fix(pos+k)] - a[pos];
a[pos ] = inib[fix(pos+k)];
}
if(a[pos] > b[lim].first)
{
swap(les[i],les[len-1]);
len--;
// 要返回一个点,否则swap 过来 原本len-1 的点不会被赋值。
i--;
}
}
// for(int i=0;i<n;i++)
// cout<<a[i]<<' ';
// cout<<endl;
cout<<ans<<endl;
}
}
signed main (){
std::ios::sync_with_stdio(false);
cin.tie(NULL);
cout.tie(NULL);
int t;
cin>>t;
while(t--)
solve();
}
题目要的是恰好 k k k 个不相交的连续子段,注意不需要 这 k k k 个连续,即不是把一整个集合划分成 k k k 个连续的段。由于要最大化最小值,显然是二分,我们可以从左往右,先前缀和,然后 set 来维护下标。然后对于每一个点,看前面和他差是 ≥ m i d \ge mid ≥mid 并且 下标之间是质数的,然后转移即可。如果不是 ≥ m i d \ge mid ≥mid 及时的 b r e a k break break 掉。这样的复杂度是 l o g n logn logn 的,因为我们打出 2 × 1 0 5 2\times 10^5 2×105 会发现基本 10 10 10 个数间就有一个质数,即我们 s e t set set 维护的东西每次最多遍历 10 10 10 次。所以最后的复杂度为 O ( n l o g n l o g m ) O(nlognlogm) O(nlognlogm)
要注意一些细节问题。
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 200000+10;
int isprime[N],pri[N];
int cnt ;
void ini()
{
for(int i=2;i<=2e5;i++)
{ if(!isprime[i]) pri[++cnt] = i;
for(int j=1;j<=cnt;j++)
{
if(i*pri[j] > 2e5) break;
isprime[i*pri[j]] = 1;
if(i%pri[j] == 0) break;
}
}
}
void solve()
{
int n,k;
cin>>n>>k;
vector<int>a(n+1);
for(int i=1;i<=n;i++)
cin>>a[i];
if(k>n/2)
{
cout<<"impossible"<<endl;
return ;
}
int l =-2e8,r = 2e8;
int ans =0;
for(int i=1;i<=n;i++)
a[i] += a[i-1];
auto jud=[&](int x)
{ set<array<int,2> > s;
//要以 a[v] 为第一键值 这样就可以及时的break。
s.insert({0,0});
int cnt = 0 ;
for(int i=1;i<=n;i++)
{ int flag = 0;
//及时的break
for(auto [u,v]:s)
{
if((a[i] - u)>=x )
{ if(!isprime[i-v])
{ flag = 1;
cnt += 1;
break;
}
}
else
break;
}
if(cnt >= k)
return 1;
if(flag)
s.clear();
s.insert({a[i],i});
//cout<<cnt<<endl;
}
return 0;
};
//cout<<jud(-9)<<endl;
while(l<=r)
{
int mid = (l+r)/2;
if(jud(mid)) // all >= mid
{
ans = mid;
l = mid+1;
}
else
r = mid-1;
}
cout<<ans<<endl;
}
signed main (){
std::ios::sync_with_stdio(false);
cin.tie(NULL);
cout.tie(NULL);
ini();
//cout<<cnt<<endl;
//要记得0和1
isprime[0] = 1;
isprime[1] = 1;
int t;
cin>>t;
while(t--)
solve();
}
我们先考虑延迟的步数怎么影响答案,会发现其实我们可以两个先走 m − k m-k m−k 步就先让他们同步, 然后剩下的 k k k 步我们
该题其实可以有很朴素的想法,就是枚举每个自己的点和对手的点,就定义 d p [ i ] [ x ] [ y ] [ e m ] [ e y ] [ h p ] dp[i][x][y][em][ey][hp] dp[i][x][y][em][ey][hp] 但是这样定义,会使得MLE 或者 TLE(因为有 t t t 组),因为 2 × 5 0 4 × 5 × t 2\times 50^4 \times 5 \times t 2×504×5×t.
我们可以考虑优化,我们会发现敌人和我们之间的距离,在移动的过程中曼哈顿距离 最多不会超过 h p hp hp, 那我们就可以把敌人的坐标表示为和初始的曼哈顿距离之差即可。就可以使得复杂度变成 2 × 5 0 2 × 5 3 × t 2\times 50^2 \times 5^3\times t 2×502×53×t
#include<bits/stdc++.h>
using namespace std;
#define int long long
int dp[2][55][55][11][11][5]; // 计算m - k 步可以让敌方失败的方案数
/*
dp[t][i][j][dx][dy][hp]: 表示在第 t 个时间, 我方在(i,j) 位置且哈密顿距离之差为 (dx,dy),敌方的血量减少了 hp
dp1[t][i][j]: 第t 个时间,在 (i,j)位置的方案数。
*/
int mid = 5;
int dp1[2][55][55]; // 随机走k步的方案数
const int mod = 1e9+7;
const int N = 55;
int n,m,k,hp;
char grid[N][N];
int x,y,ex,ey;
pair<int,int> dir[]={
{1,0},{-1,0},{0,1},{0,-1}
};
bool jud(int x,int y)
{
if(x<1 || y<1 || x>n || y>n || grid[x][y] == '#')
return true;
else
return false ;
};
void caldp1()
{
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
{
if(grid[i][j] == '#')
;
else
dp1[0][i][j] = 1;
}
for(int t=1;t<=k;t++)
{
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
{ if(grid[i][j]!='#')
for(auto [dx,dy]:dir)
{ if(jud(i-dx,j-dy))
continue ;
dp1[t&1][i][j] = (dp1[t&1][i][j] + dp1[(t-1)&1][i -dx][j-dy])%mod;
}
}
memset(dp1[(t-1)&1],0,sizeof(dp1[(t-1)&1]));
}
}
void solve()
{ memset(dp,0,sizeof(dp));
memset(dp1,0,sizeof(dp1));
cin>>n>>m>>k>>hp;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
{cin>>grid[i][j];
if(grid[i][j] == 'P')
x = i,y = j;
else if(grid[i][j] == 'E')
ex = i,ey = j;
}
int hx = ex - x ,hy = ey - y;
m -= k;
dp[0][x][y][mid][mid][0] = 1;
int ans = 0;
caldp1();
//cout<<dp1[1][2][1]<<endl;
//cout<<dp[0][2][4][4][3][4]<<endl;
for(int t=0;t<m;t++)
{
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
{
for(int subx = 0;subx<11;subx++)
{
for(int suby = 0;suby < 11;suby++)
{
for(int nowh= abs(subx-mid) + abs(suby-mid); nowh < hp; nowh++)
{ if(!dp[t&1][i][j][subx][suby][nowh])
continue ;
//cout<<"case1:"<<' '<<t<<' '<<i<<' '<<j<<' '<<subx<<' '<<suby<<' '<<nowh<<endl;
for(auto [dx,dy]:dir)
{
if(jud(dx+i,dy+j))
{
continue ;
}
if(jud(i+ hx+ mid - subx + dx , j+ hy+mid - suby + dy ))
{
if(nowh +1 == hp)
{ //cout<<"case2: "<<t<<' '<<i<<' '<<j<<' '<<subx<<' '<<suby<<' '<<nowh<<' '<<dp[t][i][j][subx][suby][nowh]<<endl;
ans =(ans+ dp[t&1][i][j][subx][suby][nowh] * dp1[k&1][i+dx][j+dy]%mod)%mod;
continue ;
}
else
{
dp[(t+1)&1][i+dx][j+dy][subx+dx][suby+dy][nowh+1] = (dp[(t+1)&1][i+dx][j+dy][subx+dx][suby+dy][nowh+1] + dp[t&1][i][j][subx][suby][nowh])%mod;
}
}
else
dp[(t+1)&1][i+dx][j+dy][subx][suby][nowh] = (dp[(t+1)&1][i+dx][j+dy][subx][suby][nowh] + dp[t&1][i][j][subx][suby][nowh])%mod;
}
}
}
}
}
memset(dp[t&1],0,sizeof(dp[t&1]));
}
cout<<ans<<endl;
}
signed main (){
std::ios::sync_with_stdio(false);
cin.tie(NULL);
cout.tie(NULL);
int t;
cin>>t;
while(t--)
solve();
}
1012
该题是 m m m 次询问 每次不能经过我们所询问的矩形区域,但是我们可以考虑每次的询问,我们怎么去处理。
不妨先定义一下 f [ i ] f[i] f[i] 表示 从 ( 0 , 0 ) → ( i , p i ) (0,0) \rightarrow (i,p_i) (0,0)→(i,pi) 的最大值 然后 g [ i ] g[i] g[i] 表示从 ( i , p i ) → ( n + 1 , n + 1 ) (i,p_i) \rightarrow (n+1,n+1) (i,pi)→(n+1,n+1) 的路径最大值
然后我们可以用 h [ i ] h[i] h[i] 表示从 ( 0 , 0 ) → ( n + 1 , n + 1 ) (0,0) \rightarrow (n+1,n+1) (0,0)→(n+1,n+1) 路径上经过 i i i 的最大值是多少。
很显然 h i = f i + g i h_i = f_i + g_i hi=fi+gi
对于 f f f 和 g g g 我们可以扫描线+ 树状数组,这是如何操作的呢,就是每一列一列的扫,我们树状数组维护每一列扫的最大值,因为有性质该序列是严格单调的,所以可以直接 lowbit 向上转移。然后查询即可。复杂度仅 O ( n l o g n ) O(nlogn) O(nlogn) 若用 d p dp dp 转移将 o ( n 2 ) o(n^2) o(n2) 显然会 TLE;
接下来我们画出如下图,对于每次询问 [ x 1 , x 2 ] × [ y 1 , y 2 ] [x1,x2]\times[y1,y2] [x1,x2]×[y1,y2] 我们都可以把该区域划分成四种区域。
第一种区域就是,我们在 f 1 f1 f1 区域里面选 f f f 一个最大值,然后 g 1 g1 g1 区域 里面选择 g g g最大值的点,这样可以使得不经过询问区域,从 f 1 → h 1 → g 1 f1 \rightarrow h1 \rightarrow g1 f1→h1→g1 这样。
第二种和上面同理,在图上是 f 2 f_2 f2 和 g 2 g_2 g2 和上面同理
然后还有两个是边角 h 1 , h 2 h1,h2 h1,h2 这两个区域我们可以直接用之前维护的 h h h 来进行计算,显然选择这两个区域的 h h h 必然不会经过查询区域。
上面区域的更新也都是扫描线 + 树状数组 跑 4 4 4 次就行了
写的时候要注意在 f 2 f_2 f2 区域我们查询的是 y 1 − 1 y1-1 y1−1 , g 1 g1 g1 也是。还要注意什么时候更新,是插入前还是后.
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define lowbit(x) (x&(-x))
const int N = 300000+10;
int tr[N]; // 维护1-x 的最大值
int n;
void initr()
{
for(int i=1;i<=n;i++)
tr[i] = 0;
}
void insert(int pos,int val)
{
while(pos <= n )
{
tr[pos] = max(tr[pos],val);
pos += lowbit(pos);
}
}
int query(int pos)
{
int res = 0;
while(pos)
{
res = max(res,tr[pos]);
pos -= lowbit(pos);
}
return res;
}
int f[N],g[N]; // f[i] 维护 (0,0) -> (i,p[i]) 的最大值, g[i] 维护 (i,p[i]) ->(n+1,n+1) 的最大值
struct Query
{
int x1,x2,y1,y2;
}Q[N];
vector<pair<int,int> > X1[N],X2[N];
int f1[N],g1[N],f2[N],g2[N],h1[N],h2[N];
void solve()
{
int m;
cin>>n>>m;
vector<pair<int,int> > v(n+1);
for(int i=1;i<=n;i++)
{
X1[i].clear();
X2[i].clear();
}
for(int i=1;i<=n;i++)
cin>>v[i].first>>v[i].second;
initr();
for(int i=1;i<=n;i++)
{
int x = query(v[i].first);
f[i] = x + v[i].second ;
insert(v[i].first,f[i]);
}
//cout<<query(3)<<endl;
initr();
for(int i=n;i>=1;i--)
{ // 可以维护距离 n 的距离。而不需要重新写函数
int x = query(n - v[i].first+1);
g[i] = x+ v[i].second ;
insert(n-v[i].first+1,g[i]);
}
// for(int i=1;i<=n;i++)
// cout<<f[i]<<' '<<g[i]<<endl;
for(int i=1;i<=m;i++)
{ cin>>Q[i].x1>>Q[i].y1>>Q[i].x2>>Q[i].y2;
X1[Q[i].x1].push_back({Q[i].y2,i});
X2[Q[i].x2].push_back({Q[i].y1,i});
}
initr();
for(int i=1;i<=n;i++)
{
for(auto [y,pos]:X1[i])
{
int val = query(y);
f1[pos] = val ;
}
insert(v[i].first,f[i]);
for(auto [y,pos]:X2[i])
{
int val = query(y-1);
f2[pos] = val;
}
}
initr();
for(int i=1;i<=n;i++)
{
for(auto [y,pos]:X1[i])
{
int val = query(n-y+1);
h1[pos] = val;
}
insert(n-v[i].first+1,f[i]+g[i] - v[i].second);
}
initr();
for(int i=n;i>=1;i--)
{
for(auto [y,pos]:X2[i])
{
int val = query(n-y+1);
g2[pos] = val ;
}
insert(n-v[i].first+1,g[i]);
for(auto [y,pos]:X1[i])
{
int val = query(n-y);
g1[pos] = val;
}
}
initr();
for(int i=n;i>=1;i--)
{
for(auto [y,pos]:X2[i])
{
int val = query(y);
h2[pos] = val;
}
insert(v[i].first,f[i]+g[i] - v[i].second );
}
// for(int i=1;i<=m;i++)
// {
// cout<<f1[i]<<' '<<g1[i]<<' '<<f2[i]<<' '<<g2[i]<<' '<<h1[i]<<' '<<h2[i]<<endl;
//
// }
for(int i=1;i<=m;i++)
{
cout<<max({f1[i]+g1[i],f2[i]+g2[i],h1[i],h2[i]})<<endl;
}
}
signed main (){
std::ios::sync_with_stdio(false);
cin.tie(NULL);
cout.tie(NULL);
int t;
cin>>t;
while(t--)
solve();
}
看见数据范围18可以想到枚举状态,但是枚举状态只可以将一个集合划分为两个,即 0 0 0 代表一个 1 1 1 代表一个。那么我们会想可不可以再继续划分呢。如果我们继续用二进制划分,如果一边划分了所有元素,那么在下面划分的时候仍然是 2 n 2^n 2n 显然超过了时间复杂度。
那么对于一个集合怎么划分成两个集合呢? 由于是异或这种运算,我们其实可以枚举一个集合就得到另一个集合。因为 x ⊕ y = s u m x o r x \oplus y = sumxor x⊕y=sumxor ,然后枚举某个集合,其实可以用 0 − 1 0-1 0−1 背包去枚举即可。集合划分后,我们可以枚举左边的值 w a , w b w_a,w_b wa,wb 右边的值 w c , w d w_c,w_d wc,wd 。可以假定 w a ≥ w b , w c ≥ w d w_a \ge w_b , w_c\ge w_d wa≥wb,wc≥wd 那么对于答案其实只有两种情况了:
w a ≥ w c w_a \ge w_c wa≥wc ,此时我们只需要看当前的 w b , w d w_b,w_d wb,wd , a n s = w a − m i n ( w b , w d ) ans = w_a - min(w_b,w_d) ans=wa−min(wb,wd)
w a ≤ w c w_a\le w_c wa≤wc, a n s = w c − m i n ( w b , w d ) ans = w_c - min(w_b,w_d) ans=wc−min(wb,wd)
显然我们不可能用枚举两个数来解决。我们可以先对原本的 w w w 去排序,然后从 w w w 小的值开始看在 左右集合中是否有该下标。然后同时维护 m x l , m x r mxl,mxr mxl,mxr 即可。
本题十分容易TLE,这里有几个点防止TLE:
- 我们可以直接把 a 1 a_1 a1 放入左边集合,这样可以少一半的常数
- 不要开long long ,取max 最好手动取
- 背包不能枚举体积,而应该枚举物品异或
- 对于在计算 w a , w b , w c , w d w_a,w_b,w_c,w_d wa,wb,wc,wd 的时候最好同时取更新答案。
最后接近时限的一半。
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N = 1<<10;
pair<int,int> w[N];
inline int umax(int x,int y)
{
return x>y?x:y;
}
inline int umin(int x,int y)
{
return x>y?y:x;
}
int val[N];
void solve()
{
int n,m;
cin>>n>>m;
vector<int> a(n+1);
for(int i=0;i<n;i++)
cin>>a[i];
for(int i=0;i<(1<<m);i++)
{ cin>>w[i].first;
w[i].second = i;
val[i] = w[i].first;
}
sort(w,w+(1<<m));
int ans = INT_MAX;
for(int i=0;i<(1<<(n-1));i++)
{ int suml = 0,sumr = 0;
vector<int> L(1<<m),R(1<<m);
vector<int> LV,RV;
L[a[0]] = 1;
LV.push_back(a[0]);
suml ^=a[0];
LV.push_back(0);
RV.push_back(0);
L[0] = 1;
R[0] = 1;
// 0-1 背包,但是可以枚举物品,这样复杂度不会每次都是 1<<m 只有最后一次才是。如果按照枚举空间,那么就是O(n2^m) 会TLE
//因为x ^ y ^y = x ,所以可以直接按照完全背包来。
for(int j=0;j<n;j++)
{
if(i&(1<<j))
{ int len = LV.size();
for(int i=0;i<len;i++)
{ int v = LV[i];
if(L[v^a[j+1]])
continue ;
L[v^a[j+1]] = 1;
LV.push_back(v^a[j+1]);
}
suml ^= a[j+1];
}
else
{ int len = RV.size();
for(int i=0;i<len;i++)
{ int v = RV[i];
if(R[v^a[j+1]])
continue ;
R[v^a[j+1]] = 1;
RV.push_back(v^a[j+1]);
}
sumr ^= a[j+1];
}
}
vector<pair<int,int> > mxl,mxr;
int mxvl = -1,mxvr = -1;
int nowans = INT_MAX;
for(int k = 0; k<(1<<m);k++)
{
if(L[w[k].second] && val[w[k].second] >= val[w[k].second ^ suml])
{
mxvl = umax(mxvl,val[w[k].second ^ suml]);
if(~mxvr)
nowans = umin(nowans,val[w[k].second] - umin(val[w[k].second ^ suml] ,mxvr ) );
}if(R[w[k].second] && val[w[k].second] >= val[w[k].second^sumr])
{
mxvr = umax(mxvr,val[w[k].second ^ sumr]);
if(~mxvl)
nowans = umin(nowans,val[w[k].second] - umin(val[w[k].second ^ sumr] ,mxvl ) );
}
}
//cout<<"case1:"<<' '<<i<<' '<<nowans<<endl;
ans = min(nowans,ans);
}
cout<<ans<<endl;
}
signed main (){
std::ios::sync_with_stdio(false);
cin.tie(NULL);
cout.tie(NULL);
int t;
cin>>t;
while(t--)
solve();
}