2022 ICPC 亚洲区域赛南京站(A,B,D,G,M)

题意理解:给定一个m*n的区域,这片区域某个地方存在一个洞,其他地方都有袋鼠,袋鼠会经过一系列运动,可能出界或者掉入洞内,现在给出经过运动变化后剩余袋鼠数量,求洞可能在的位置的可能性

思路解析:

            1.将袋鼠的运动转化成矩形形状的变换,经过一系列的运动后,矩形会相应被裁减成一个小矩形,这个小矩形的大小是如果不存在洞的情况下袋鼠剩余个数

             2.所以没有出网格的袋鼠数量是x=D-U+1)*(R-L+1),因为最终剩余k只袋鼠,那么就要有x-k只袋鼠掉到洞中

             3.我们来统计这个小矩形里活的袋鼠经过在移动过程中经过的格子,也就是统计m*n个格子中每个经过了这个小矩形中多少只袋鼠(注意这个洞可以在m*n的任意位置,而并非只是这个小矩形)然后计算经过数目为(x-k)的格子的数目

                  这个可以用二维差分,每次移动得到++的矩形范围,在此范围内差分,由于这个小矩形的大小已经确定,并且肯定不会出界,所以判重可以直接用左上角的坐标来判,这样就保证了每只袋鼠对于每个格子只统计一次

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>

using namespace std;
const int N=1010;
int n,m,k;
string s;
int U,D,L,R,u,d,l,r;
bool vis[N][N];
int f[N][N];

void add(int x1,int y1,int x2,int y2)
{
	f[x1][y1]++;
	f[x2+1][y1]--;
	f[x1][y2+1]--;
	f[x2+1][y2+1]++;
}
void solve()
{
	memset(f,0,sizeof f);
	memset(vis,0,sizeof vis);
	cin>>n>>m>>k>>s;
	U=L=u=l=1;
	R=r=m;
	D=d=n;
	for(int i=0;s[i];i++)
	{
		if(s[i]=='D')u--,d--;
		else if(s[i]=='U')u++,d++;
		else if(s[i]=='L')r++,l++;
		else r--,l--;
		L=max(L,l);
		R=min(R,r);
		U=max(U,u);
		D=min(D,d); 
	}
	
	if(L>R||U>D)
	{
		if(k)cout<<0<<endl;
		else cout<<m*n<<endl;
		return;
	}
	int delta=(D-U+1)*(R-L+1)-k;
	if(delta<0)
	{
		cout<<0<<endl;
		return;
	}
	
	add(U,L,D,R);
	vis[L][U]=1;
	for(int i=0;s[i];i++)
	{
		if(s[i]=='L')L--,R--;
		else if(s[i]=='R')L++,R++;
		else if(s[i]=='U')U--,D--;
		else U++,D++;
		if(vis[L][U])continue;
		vis[L][U]=1;
		add(U,L,D,R); 
	}
	
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++)
		{
			f[i][j]+=f[i-1][j]+f[i][j-1]-f[i-1][j-1];
		}
	}
	int ans=0;
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++)
		{
			if(f[i][j]==delta)ans++;
		}
	}
	cout<<ans<<endl;
}
int main()
{
	int t;
	cin>>t;
	while(t--)
	{
		solve(); 
	}
}

                               

题意:可以看作一个数轴上有n个点,选取一些点连接,这些点要满足距离不大于k,且这些点将首末相连接,每个点使用的价格不同,接下去有q次临时修改一个点的价格,再求其对应的最小价格

分析:1.如果不做修改的话,这是一个典型的DP问题,每一个点i 可以由前面的j点转移而来,j满足(i-k<=j<i),可以用单调队列来优化,得到O(N)复杂度

得到转移方程:f[i]=min(f[j])+w[i]

            2.如果修改的话,对p的改变,会影响p之后所有的点的f[i],时间复杂度达到O(N*Q),需要优化

           3.由于只有一个方向的递推,所以影响会一直持续到最后,但是如果从反方向再来一遍递推,也就是预处理出来从n+1向0修建到i所需要的最小代价

转移方程:g[i]=min(g[j]+w[i](i<j<=i+k)

那么,在修建当前点的前提下,总的修建代价就可以得到

VALUE=g[i]+f[i]+w[i]

这样子做的好处在于,对于每一处的修改,它的影响在于自身的w,以及(i,i+k]范围内的f[j],所以时间复杂度就可以降到O(n+kq)

            

     tips:

       1.不用stl的话,手写双端队列,我也不知道为什么会TLE

       2.大量的读入必须要用scanf,否则第二个点就tle

       3.数值很大用long long,相对应的max要写成(long long)1e18,不然第二个点就错

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<deque> 
#define INF ((long long) 1e18)

using namespace std;
const int N=5e5+10;
typedef pair<long long,int>pii;
int n,k,t;
long long a[N],f[N],g[N],h[N];
char must[N];
int Q;
int p,v;
int q[N];
void dp(long long f[])
{
//	int hh=0,tt=-1;
//	q[++tt]=0;
//	for(int i=1;i<=n+1;i++)
//	{
//		while(hh<=tt&&i-q[hh]>k)hh++;
//		if(hh<=tt)f[i]=f[q[hh]]+a[i];
//		if(must[i]=='1')//这个点必须建,那么它后面的点以它作为滑动窗口的开头是最优的 
//		{
//			hh=0,tt=-1;
//		}
//		while(hh<=tt&&f[i]<=f[q[tt]])tt--;
//		q[++tt]=i;
//	}
    deque<pii>dq;
    f[0]=0;
    dq.push_back(pii(0,0));
	for(int i=1;i<=n+1;i++)
	{
		while(dq.front().second<i-k)dq.pop_front();
		f[i]=dq.front().first+a[i];
		if(must[i]=='1')dq.clear();
		while(!dq.empty()&&dq.back().first>=f[i])dq.pop_back();
		dq.push_back(pii(f[i],i));
	 } 
}
long long  dp2(int x,int y)//把a[x]改成y 
{
	
	int tmp=a[x];
	a[x]=y;
	
	long long res=INF;
//	int hh=0,tt=-1;
//	//将f[x-k]--f[x-1]加入单调队列 
//	for(int i=k;i>0;i--)
//	{
//		
//		if(x-i>=0)
//		{
//			if(must[x-i]=='1')
//			{
//				hh=0,tt=-1;
//			}
//			while(hh<=tt&&f[x-i]<=f[q[tt]])tt--;
//			q[++tt]=x-i;
//		}
//	}
//	memcpy(h,f,sizeof f);
//	//重新计算f[x]--f[x+k-1]
//	 for(int i=x;i<=n+1&&i<x+k;i++)
//	{
//		while(hh<=tt&&i-q[hh]>k)hh++;
//		if(hh<=tt)h[i]=h[q[hh]]+a[i];
//		res=min(res,h[i]+g[i]);
//		if(must[i]=='1')
//		{
//			hh=0,tt=-1;
//		}
//		while(hh<=tt&&h[i]<=h[q[tt]])tt--;
//		q[++tt]=i;
//	}
//	
    deque<pii> dq;
    for (int i = k; i > 0; i--) 
	if (x - i >= 0) {
        if (must[x - i] == '1') dq.clear();
        while (!dq.empty() && dq.back().first >= f[x - i]) dq.pop_back();
        dq.push_back(pii(f[x - i], x - i));
    }
    
    for (int i = x; i < x + k&& i <= n + 1; i++) {
        while (dq.front().second < i - k) dq.pop_front();
        h[i] = dq.front().first + a[i];
        // 计算每个中间点的答案
        res = min(res, h[i] + g[i]);
        if (must[i] == '1') dq.clear();
        while (!dq.empty() && dq.back().first >= h[i]) dq.pop_back();
        dq.push_back(pii(h[i], i));
    }

	a[x]=tmp;
	return res;
	
	
	
}
void solve()
{
	cin>>n>>k;
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
	}
	a[n+1]=0;
	
	scanf("%s", must + 1);
	
	dp(f);


	reverse(a+1,a+n+1);
	reverse(must+1,must+n+1);
	dp(g);

	
	reverse(a+1,a+n+1);
	reverse(must+1,must+n+1);
	reverse(g,g+n+2);
	
	for(int i=1;i<=n;i++)g[i]-=a[i];//g表示i点建立下,后面所有点的最小支出 
	
	cin>>Q;
	while (Q--) {
        int x, y; scanf("%d%d", &x, &y);
        printf("%lld\n", dp2(x, y));
    }
}
int main()
{
	cin>>t;
	while(t--)
	{
		solve();
	}
}

题意:

给定一个长度为n的数组,可以在任意长度为m的连续子数组上加一个等差序列,要求这样子操作一次之后得到的序列的第k大值最大

分析

         1.要求第k大值的最大值,第k大值意味着这个数组经过操作后中有k-1个数字比这个数字大,所以考虑二分,二分这个第k大数的值,然后统计经过最优操作后,能比这个k值大的数的个数,如果这个个数大于k,说明我们的x取小了,反之,x取大了

         2.如果找到最优的方法呢,其实也就是让大于x的数最多的方法(我们计大于x的数标志为1,小于x的为0)

            2.1首先,对于数组进行第一次遍历,所有大于x的数打上标记,并统计其个数

            2.2然后可以想象,我们的等差数列数组是从左到右依次变大的,然后在从左到右移动其控制范围的过程中,每一个数没有被打上标记的数在它作用过程中,先变到最大,然后逐渐变小,也就是它的标志会先从0-->1,再从1-->0

             2.3当作用范围最右端为i时,统计从i-1变到i造成的数据标记变化量,记为f[i]

             2.4最后f[i]表示的是,作用范围最右端在这的时候,对于大于x值得数量得贡献量,也就是说,f[]的最大前缀和就是能通过增加这个等差数列而多的满足大于x的值,再与一开始就满足的个数相加,与k比较,得到返回结果

举个例子

 举个例子

 

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#define int long long
using namespace std;
const int N=2e5+10;

int n,k,m,c,d;
int a[N];
int tag[N];
int f[N];
int maxn;
bool check(int x)
{
	memset(tag,0,sizeof tag);
	int res=0;
	for(int i=1;i<=n;i++)
	{
		if(a[i]>=x)
		{
			res++;
			tag[i]=1;
			if(res>=k)return true;//一定加上,因为一开始mid很大,数据过多的情况下不及时return会超时
		}
	}
	memset(f,0,sizeof f);
	
	for(int i=1;i<=n;i++)
	{
		if(!tag[i])
		{
			int r=min(m-1,i-1);
			if(a[i]+d*r+c<x)continue;//如果最大的也小于那就不用考虑了
			else 
			{
				f[max(i,m)]++;//首先它会对当前这个位置产生+1的影响
				if(a[i]+c>=x)f[min(n,i+m)]--;//如果最小的加上也大于x,那么就是它离开控制范围的时候--
				else
				{
			       long long t = x- a[i] - c;
                   int pos;
                   if (t % d == 0) pos = t / d - 1;
                   else pos = t / d;
                   f[min(n + 1, i + m - pos - 1)]--;
				}
			}
		}
	}
	for(int i=m;i<=n+1;i++)
	{
		if(res>=k)return true;//计算最长前缀和
		res+=f[i];
	}
	return false;
}
signed main()
{
	cin>>n>>k>>m>>c>>d;
	for(int i=1;i<=n;i++)
	{
		cin>>a[i];
	}
	int ll=0,rr=1e18;
	maxn=c+(m-1)*d;
	while(ll<rr)
	{
		int mid=(ll+rr+1)/2;
		if(check(mid))
		{
		   ll=mid;
		}
		else rr=mid-1;
	}
	cout<<ll<<endl;
}

tips:

1.对于大量数据的二分,及时return 

2.这道题目应该用>=mid来做,如果是>mid的话,实际上把这个数缩小了,只要让小于这个的最大的数排列在k后面就好,所以在对于它影响的--的位置,要分成能不能整除两种情况计算

题意:

一路上有n个事件,每个事件都有三种情况 ,求最后能得到的最大平均

分析:

          1.第一二种比较下来,明显是第一种对于目标平均值的优势更大,所以第三种下,我们尽可能选第二种

           2.但是选第二种有个条件是必须有两个野兽,并且会导致消失一个野兽,如果一味选择第二种情况,就会导致很快的结束

           3.在面对3的情况时,我们只要可以,就选第二种,如果不行再选第一种,并且记录下选第二种的次数,第二种情况是决定生死的情况,所以如果自身的不够,可以反悔之前对于情况三的选择,当时我们是将野兽数目-1,那么现在反悔就是野兽数目加二,同时献祭一个,也就是野兽数目+1

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;

int n;
int x;

int main()
{
	int T;
	scanf("%d",&T);
	while(T--)
	{

	scanf("%d",&n);
	int sum=1,cnt=1,choice=0;
	bool flag=0;
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&x);
		if(x==1)sum++,cnt++;
		else if(x==-1)
		{
			if(cnt>1)cnt--;
			else if(choice>=1)choice--,sum++,cnt++;
			else flag=1;
		}
		else
		{
			if(cnt>1)cnt--,choice++;
			else sum++,cnt++;
		}
	}
	if(flag)printf("-1\n");
	else
	{
		cout<<sum/__gcd(sum,cnt)<<" "<<cnt/__gcd(sum,cnt)<<endl;
	}
	}
	return 0;
}

 题意:

        一个水箱需要几个水阀门能将水放干净

分析:

       1.找的是局部最低点,这样的最低点有两种情况,第一种是尖头,类似于v型,第二种则是从高到低再保持直线型的,两种情况不一样

        2.对于v型的,必须是从左到右先下后上,也就是叉积>0

        3.对于底面平的,没有办法用叉积来计算,判断其x的大小

 

#include<iostream>
#include<algorithm>

using namespace std;
const int N=2010;
int n,ans;
typedef long long ll;
ll x[N],y[N];

ll cross(ll x1,ll y1,ll x2,ll y2)
{
	return x1*y2-x2*y1;
}
int main()
{
	cin>>n;
	for(int i=0;i<n;i++)cin>>x[i]>>y[i];
	
	for(int i=0,j=1;i<n;i++)
	{
		while(y[i]==y[j])j=(j+1)%n;
		int pre=(i+n-1)%n;
		if(y[i]<y[pre]&&y[i]<y[j])
		{
			if(y[i]!=y[(i+1)%n])
			{
				if(cross(x[i]-x[pre],y[i]-y[pre],x[j]-x[i],y[j]-y[i])>0)ans++;
			}
			else 
			{
				if(x[(i+1)%n]>x[i])ans++; 
			}
		}
	}
	cout<<ans<<endl;
}

  • 6
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值