2024杭电多校第4场 7|3|6|12|4

第四场 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((1nx)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();
} 

1003

题目要的是恰好 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();
} 

1006

我们先考虑延迟的步数怎么影响答案,会发现其实我们可以两个先走 m − k m-k mk 步就先让他们同步, 然后剩下的 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 f1h1g1 这样。

第二种和上面同理,在图上是 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 y11 , 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();
} 

1004

看见数据范围18可以想到枚举状态,但是枚举状态只可以将一个集合划分为两个,即 0 0 0 代表一个 1 1 1 代表一个。那么我们会想可不可以再继续划分呢。如果我们继续用二进制划分,如果一边划分了所有元素,那么在下面划分的时候仍然是 2 n 2^n 2n 显然超过了时间复杂度。

那么对于一个集合怎么划分成两个集合呢? 由于是异或这种运算,我们其实可以枚举一个集合就得到另一个集合。因为 x ⊕ y = s u m x o r x \oplus y = sumxor xy=sumxor ,然后枚举某个集合,其实可以用 0 − 1 0-1 01 背包去枚举即可。集合划分后,我们可以枚举左边的值 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 wawb,wcwd 那么对于答案其实只有两种情况了:

w a ≥ w c w_a \ge w_c wawc ,此时我们只需要看当前的 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=wamin(wb,wd)

w a ≤ w c w_a\le w_c wawc, a n s = w c − m i n ( w b , w d ) ans = w_c - min(w_b,w_d) ans=wcmin(wb,wd)

显然我们不可能用枚举两个数来解决。我们可以先对原本的 w w w 去排序,然后从 w w w 小的值开始看在 左右集合中是否有该下标。然后同时维护 m x l , m x r mxl,mxr mxl,mxr 即可。

本题十分容易TLE,这里有几个点防止TLE:

  1. 我们可以直接把 a 1 a_1 a1 放入左边集合,这样可以少一半的常数
  2. 不要开long long ,取max 最好手动取
  3. 背包不能枚举体积,而应该枚举物品异或
  4. 对于在计算 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();
} 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值