2019-10-22

T1

一道很水的题 其实对于本蒟蒻来说也没有那么水 只需要知道最小值总是会在零点的时候取到 因此将零点排序之后枚举当(x=)每个零点时 整个式子的值 然后用ans来存最小值。
需要注意的地方:1.因为我用的方法是用前缀和,后缀和优化,所以一定要将零点排序
2.当(a=0)的时候 零点算不出来。。我是将零点赋为极大值
3.当(a<0)的时候 将(a,b)都取反
因为在零点左边的所有式子一定大于零 在零点右边的式子一定小于零 所以可以用前缀和后缀和来实现

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#define ll long long 
using namespace std;
const ll maxn=300010;
const ll inf=0x3f3f3f3f;
ll read()
{
	ll x=0,f=1;
	char ch=getchar();
	while(ch<'0' || ch>'9')
	{
		if(ch=='-')	f=-1;
		ch=getchar();
	}
	while(ch>='0' && ch<='9')
	{
		x=x*10+ch-'0';
		ch=getchar();
	}
	return x*f;
}

ll n,prea[maxn],preb[maxn],sufa[maxn],sufb[maxn];
ll a[maxn],b[maxn];
struct node{
	ll prea,preb,sufa,sufb,id,a,b;
	long double zero;
	#define prea(x) job[x].prea
	#define preb(x) job[x].preb
	#define sufa(x) job[x].sufa
	#define sufb(x) job[x].sufb
	#define id(x) job[x].id
	#define a(x) job[x].a
	#define b(x) job[x].b
}job[maxn];
long double ans=999999999999999,now,bj;

inline bool cmp(node x,node y)
{
	return x.zero<y.zero || (x.zero==y.zero && x.id<y.id);
}

int main()
{
	freopen("spongebob.in","r",stdin);
	freopen("spongebob.out","w",stdout);
	n=read();
	for(ll i=1;i<=n;i++)
	{
		a(i)=read(),b(i)=read();
		if(a(i)<0){
			a(i)=-a(i);b(i)=-b(i);
		}
		else if(a(i)==0){
			job[i].zero=0x3f3f3f3f*1.0;
		}
		job[i].zero=-1.0*(double)b(i)/(double)a(i);	
	}
	sort(job+1,job+1+n,cmp);
	for(int i=1;i<=n;i++)
	{
		prea(i)=prea(i-1)+a(i);
		preb(i)=preb(i-1)+b(i);
	}
	for(ll i=n;i>=1;i--)
	{
		sufa(i)=sufa(i+1)+a(i);
		sufb(i)=sufb(i+1)+b(i);
	}
	for(ll i=1;i<=n;i++)
	{
		now=0;
		now=job[i].zero*prea(i)*1.0+preb(i)*1.0-job[i].zero*sufa(i)*1.0-sufb(i)*1.0;
		if(ans>now)	ans=now,bj=job[i].zero;
	}
	printf("%.5f",(double)ans);
	return 0;
}

T2

这道题暴力可以拿(30pts)呢! 所以说为什么我的分块才(20pts)蒟蒻表示一脸懵逼
那么暴力的方法就不用说了吧 每次都扫描一遍。
然后我们来看正解
首先分析如果对于任意的(i∈[0,n-1]),(h[i] < h[i+ 1]) 均可以对([a[i]+1,a[i+1]])造成一点贡献 当水平线到了([a[i]+1,a[i+1]])时,因为(a[i])小于该区间,(a[i+1])等于该区间 所以当水平线在这个范围内 相当于会新增加一个岛屿 所以最终你需要输出的答案就是当前水平线对应的值是多少
因为这个是待修的 所以可以用线段树和树状数组
那么接下来就上代码啦!

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int maxn=600010;

int read()
{
	int x=0,f=1;
	char ch=getchar();
	while(ch<'0' || ch>'9')
	{
		if(ch=='-')	f=-1;
		ch=getchar();
	}
	while(ch>='0' && ch<='9')
	{
		x=x*10+ch-'0';
		ch=getchar();
	}
	return x*f;
}

int h[maxn],n,m;
int tree[maxn];
int last=0;

int lowbit(int x)
{
	return x&(-x);
}

void add(int x,int val)
{
	x++;
	for(;x<=maxn;x+=lowbit(x)) 
		tree[x]+=val;
}
int query(int x)
{
	int ans=0;
	for(;x;x-=lowbit(x)) 
		ans+=tree[x];
	return ans;
}
int main()
{
	n=read();m=read();
	for(int i=1;i<=n;i++)
		h[i]=read();
	for(int i=1;i<=n;i++)
	{
		if(h[i-1]<h[i])
		{
			add(h[i-1],1);
			add(h[i],-1);
		}
	}
	for(int i=1;i<=m;i++)
	{
		char type;
		scanf("%s",&type);
		if(type=='Q')
		{
			int q=read();
			q=q^last;
			printf("%d
",last=query(q));
		}
		
		else
		{
			int x=read(),y=read();
			x=x^last;y=y^last;
			if(h[x-1]<h[x])
			{
				add(h[x-1],-1);add(h[x],1);
			}	
			if(h[x]<h[x+1])
			{
				add(h[x],-1);add(h[x+1],1);
			}
			h[x]=y;
			if(h[x-1]<h[x])
			{
				add(h[x-1],1);
				add(h[x],-1);
			}
			if(h[x]<h[x+1])
			{
				add(h[x],1);
				add(h[x+1],-1);
			}
		}
	}
	return 0;
}

T3

这道题 我觉得我不会很正常(会了才奇怪呢)
暴力:每一条边都枚举其情况 复杂度(O(2^m),20pts)
正解:首先易证:此题肯定有解!
对于每一个点如果同一个权值的边出现了两次 那么就可以考虑将这两条边删了 加入一条新的边 关于这道题的细节真的很多 具体的话看代码 我tm原地转圈圈懵逼

#include<bits/stdc++.h>
using namespace std;
#define in read()
int in{
	int cnt=0,f=1;char ch=0;
	while(!isdigit(ch)){
		ch=getchar();if(ch=='-')f=-1;
	}
	while(isdigit(ch)){
		cnt=cnt*10+ch-48;
		ch=getchar();
	}return cnt*f;
}

struct node{
	int id,to,d;
}a[2][2000003];
 
int n,m;
int depend[2000003];
int rev[2000003];
//rev 数组 解决的问题是 在最后的时候 ans[i] 由其 depend的边转换过来时 是否需要取反 
int ans[2000003];int tot;
void add(int x,int y,int id,int r){//x,y表示连接的点 id表示正在加入第i条边 r表示权值 
	node*q=a[r];// q表示所有的 权值为r的边
	//q[x].id 表示当前x 的边的编号
	//q[x].to 表示当前边指向的点  
	if(q[x].id&&q[x].to==y){//相当于 两个点 之间连了 两条边 都要删掉 
		depend[q[x].id]=depend[id]=0;//当前正在加入的边 和 之前该点对应的边 的依靠 为0 
		ans[id]=1;ans[q[x].id]=q[y].d;//当前边ans 设为 1 之前边 ans 设为 y的上一条边的 方向 
		q[x].id=q[y].id=0;//把他们的id 设为 0 相当于删边 
		return;
	}
	int flag=1;
	if(q[x].id){//如果 x 之前有边 
		flag=0;
		depend[q[x].id]=tot;// 那么x之前的边 依靠于新边 
		rev[q[x].id]=q[x].d;//rev 对于x来说 他之前边的方向 与 其指向相同 
		//而在实际计算中后面对ans 的计算中 其实是取反了的84行 
		//因为对于q[x].id这条边来说 他与新加入的边的关系 相同的
		//若 d==1 则相当于之前的边是指向 x的 
		//若 新边的方向为 0 那么 这一条边的方向不变
		//否则 会改变
		//建议手玩一下 
		q[x].id=0;//把x 之前的边 删了 
		x=q[x].to;//将x指向 之前边指向的另一个点 
	}
	//与 x同理 
	if(q[y].id){
		flag=0;
		depend[q[y].id]=tot; 
		rev[q[y].id]=q[y].d^1; //这里与x刚好相反 
		q[y].id=0;
		y=q[y].to;
	}
	q[x].to=y;q[y].to=x;//互相 成为 对方到达的点 
	q[x].d=1;q[y].d=0;//钦定方向 默认 从 x -> y的 方向  
	if(flag)q[x].id=q[y].id=id; //他们的 id 相当于 当前加入的边 
	else{
		q[x].id=q[y].id=depend[id]=tot++;//将他们的 id 设为 新点 并且 将他们的依靠 设为新边 
		//至于关于depend的疑惑 实际在 38 和 42行就已经处理过了 
	}
	//相当于 如果 flag == 0 就先当于 加边之后有环了  所以可以将xy对应的之前的边以及当前加入的边 依靠在新边上 
}
int vis[2000003];
void work(int u){
	int x=u,t=0;
	vis[x]=1;
	//t是权值  x是点 a[t][x]相当于 x点在权值t下 对应的唯一一条边 
	//这里相当于从边权为1的边开始走 
	while(a[t][x].id){
		ans[a[t][x].id]=a[t][x].d;//先 将当前边的ans暂且的设为该边本身的方向 
		if(vis[a[t][x].to])return;//如果出现环了 就返回 因为继续走下去 他还是个环。
		// 不用担心ans没更新完 因为主函数里面 是一个for 循环 
		vis[a[t][x].to]=1;
		x=a[t][x].to;t^=1;
	}
	//这里相当于从边权为2的边开始走 
	x=u;t=1;
	while(a[t][x].id){
		ans[a[t][x].id]=a[t][x].d^1;
		if(vis[a[t][x].to])return;
		vis[a[t][x].to]=1;
		x=a[t][x].to;t^=1;
	}
}
signed main(){
	n=in;m=in;tot=m+1;
	for(int i=1;i<=m;i++){
		int a=in;int b=in;int c=in;
		add(a,b,i,c-1);//第i条边 对应c-1的权值 连接的是a,b 
	}
	for(int i=0;i<n;i++)if(!vis[i])work(i);
	for(int i=tot;i>0;i--){
		if(depend[i])ans[i]=ans[depend[i]]^rev[i];//rev 如果为1 就要取反 否则 就不取反 
	}
	for(int i=1;i<=m;i++)cout<<ans[i];
	return 0;
} 
原文地址:https://www.cnblogs.com/mendessy/p/11722859.html