191022CSP模拟

T1:spongebob

传送门

法一:遇到这个绝对值,很容易想到找零点,我于是就打了80分的暴力,但细节没处理好,就直接爆了5分;实际上,想到找零点后可以很自然地过渡到零点分段,先预处理每一个零点,再排序,二分找出即可。

法二:将|ax+b|这个函数图像画出,可知它是一个下凸函数,而下凸函数的和仍然是下凸函数,则可以用三分做。

代码都很类似,下面贴的是二分:

#include
#define ll long long
#define db double
#define re register
#define cs const
#define N 300005
using namespace std;
inline int read()
{
	int x=0,f=1;
	char ch;
	while(ch>'9'||ch<'0')
	{
		if(ch=='-')	f=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9')
	{
		x=x*10+ch-'0';
		ch=getchar();
	}
	return f*x;
}
db w[N];
int n,a[N],b[N],l,r;
db work(int x)
{
	db ans=0;
	for(int i=1;i<=n;i++)
		ans+=abs(a[i]*w[x]+b[i]);
	return ans;
}
int main()
{
	n=read();
	for(int i=1;i<=n;i++)
	{
		a[i]=read();
		b[i]=read();
		w[i]=-(db)b[i]/a[i];
	}
	sort(w+1,w+1+n);
	l=1,r=n;
	while(l+2<r)
	{
		int l1=(l+r)/2;
		int r1=(l+r)/2+1;
		if(work(l1)<work(r1))	r=r1;
		else l=l1;
	}
	db sum=1e18;
	for(int i=l;i<=r;i++)	sum=min(sum,work(i));
	printf("%lf",sum);
	return 0;
}

T2:patrick

传送门

30pts暴力:

询问时搜索一遍数列即可,复杂度为(n*m);

60pts暴力:

考虑没有C操作的情况,预处理每个1-maxh的岛屿个数,加上前面的30,期望得分60pts

100pts正解:

我们可以用线段树来处理,线段树下标为高度,值为岛屿个数,后面即可单点查询,区间修改,如果a[i] < a[i+1],那么在一个数据结构内,我们将a[i]+1到a[i+1]区间加1,因为其可能出现相等情况,故加一.

树状数组也可以做,但我认为线段树好理解点。
代码:

#include
#define db double
#define re register
#define cs const
#define N 600005
using namespace std;
inline int read()
{
	int x=0,f=1;
	char ch;
	while(ch>'9'||ch<'0')
	{
		if(ch=='-')	f=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9')
	{
		x=x*10+ch-'0';
		ch=getchar();
	}
	return f*x;
}
int a,b,h[N],tree[N*4],n,m,last;
char s[100];
void add(int k,int l,int r,int ll,int rr,int val)
{
	if(l>=ll&&r<=rr)	tree[k]+=val;
	else
	{
		int mid=(l+r)/2;
		if(ll<=mid)	add(k<<1,l,mid,ll,rr,val);
		if(mid<rr)	add(k<<1|1,mid+1,r,ll,rr,val);
	}
}
int query(int k,int l,int r,int pos)
{
	int ans=0;
	ans+=tree[k];
	if(l==r)	return ans;
	int mid=(l+r)/2;
	if(pos<=mid)	ans+=query(k<<1,l,mid,pos);
	else	ans+=query(k<<1|1,mid+1,r,pos);
	return ans;
}
int main()
{
	n=read();
	m=read();
	h[0]=-1;
	for(int i=1;i<=n;i++)	h[i]=read();
	for(int i=1;i<=n;i++)	if(h[i-1]<h[i])	add(1,0,N,h[i-1]+1,h[i],1);
	if(n<=5000&&m<=5000)
	{
		while(m--)
		{
			scanf("%s",s);
			if(s[0]=='Q')
			{
				a=read();
				a=a^last;
				int ans=0;
				for(int i=1;i<=n;i++)	if(h[i]>=a&&h[i-1]<a) ans++;
				printf("%d\n",ans);
				last=ans;
			}
			else
			{
				a=read();
				b=read();
				h[a^last]=b^last;
			}
		}
	}
	else
	{
		while(m--)
		{
			scanf("%s",s);
			if(s[0]=='Q')
			{
				a=read();
				a=a^last;
				a=query(1,0,N,a);
				printf("%d\n",a);
				last=a;
			}
			else
			{
				a=read();
				b=read();
				a=a^last;
				b=b^last;
				if(h[a-1]<h[a])	add(1,0,N,h[a-1]+1,h[a],-1);
				if(h[a]<h[a+1]&&a<n)	add(1,0,N,h[a]+1,h[a+1],-1);
				h[a]=b;
				if(h[a-1]<h[a])	add(1,0,N,h[a-1]+1,h[a],1);
				if(h[a]<h[a+1]&&a<n)	add(1,0,N,h[a]+1,h[a+1],1);
			}
		}
	}
}

树状数组:

#include 

using namespace std;

const int MX = 500005;

struct FEN
{
	int sum[MX];
	
	void add(int p, int x)
	{
		for(int i=p+1; i; i-=i&-i) sum[i] += x;
	}
	
	int qur(int p)
	{
		int ret = 0;
		for(int i=p+1; i<MX; i+=i&-i) ret += sum[i];
		return ret;
	}
} F;

template <typename T> void read(T &x)
{
	x = 0; char c = getchar(); bool f = 0;
	while(!isdigit(c) && c!='-') c = getchar();
	if(c == '-') f = 1, c = getchar();
	while(isdigit(c)) x = x*10+c-'0', c = getchar();
	if(f) x = -x;
}

int n, m;
int h[MX];

void input()
{
	read(n), read(m);
	for(int i=1; i<=n; i++) read(h[i]);
}

void work()
{
	int lans = 0;
	for(int i=1; i<=n; i++)
		if(h[i-1] < h[i])
			F.add(h[i], +1), F.add(h[i-1], -1);
	for(int i=1; i<=m; i++)
	{
		int a, b;
		char c = getchar();
		while(!isalpha(c)) c = getchar();
		if(c == 'Q')
		{
			read(a);
			a ^= lans;
			printf("%d\n", lans = F.qur(a));
		}
		else
		{
			read(a), read(b);
			a ^= lans, b ^= lans;
			if(h[a-1] < h[a]) F.add(h[a], -1), F.add(h[a-1], +1);
			if(h[a] < h[a+1]) F.add(h[a+1], -1), F.add(h[a], +1);
			h[a] = b;
			if(h[a-1] < h[a]) F.add(h[a], +1), F.add(h[a-1], -1);
			if(h[a] < h[a+1]) F.add(h[a+1], +1), F.add(h[a], -1);
		}
	}
}

int main()
{
	freopen("patrick.in","r",stdin);
	freopen("patrick.out","w",stdout);
	input();
	work();
	return 0;
}

T3:eugene

传送门

这道TMD是他们noi的T1.

题意可转化为:给定一张n点m边无向图,边有权值,权值仅能为1或2(对于图上每个点,所有与其相连的边权值总和为奇数),要求给定一个给边定向的方案,使得对于每个点而言,入度和出度相差不超过1

20pts暴力:

暴力枚举每一条边。

40pts暴力;

考虑点数非常少的情况,对于两个点x;y,若连接他们的边中有多条权值相同的边,可以每两条分一组,方向互异消去这两条边。如此以来图中便剩下最多(n-1)n条边,枚举这些边即可。

100pts正解:

详见fsy和wyh巨佬的blog,我是真的菜鸡,虽然写了注释,但还是理解不到 。

fsy
wyh

代码:

#include 

template <typename _tp> inline void read(_tp&x){
	char ch=getchar(),ob=0;x=0;
	while(ch!='-'&&!isdigit(ch))ch=getchar();if(ch=='-')ob=1,ch=getchar();
	while(isdigit(ch))x=x*10+ch-'0',ch=getchar();if(ob)x=-x;
}

const int N = 1001000, M = N + N;
int dir[2][N];//这条边对于我来说,是加贡献,还是减贡献(相当于钦定了方向)
			//根据题意,我们规定0表示这个点得到贡献,1表示这个点减去贡献。
int rev[M], ans[M], depend[M];//depend存这个点删边依赖的边的编号 
int vis[N];//rev 数组 解决的问题是 在最后的时候 ans[i] 由其 depend的边转换过来时 是否需要取反 
int n, m, tot;

struct node {
	int id, to;//id:与该点相连的边的编号。
}a[2][N];//to:这条边对面的点号。

// a表示所有的 权值为t的边
void add(int x, int y, int id, int t) {//x,y表示连接的点 id表示正在加入第i条边 t表示权值 
	if(a[t][x].id&&a[t][x].to == y) {//如果x,y曾经有边,就会产生需要删除的情况。
		depend[a[t][x].id] = depend[id] = 0;//当前正在加入的边和之前该点对应的边的依靠为0 
		ans[id] = 1, ans[a[t][x].id] = dir[t][y];//当前边ans 设为 1 之前边 ans设为 y的上一条边的方向 
		a[t][x].id = a[t][y].id = 0;//把他们的id 设为 0 相当于删边 
		return ;
	}
	bool flg = true;
	if(a[t][x].id) {//如果 x 之前有边 
		flg = false;
		depend[a[t][x].id] = tot;// 那么x之前的边 依靠于新边 
		rev[a[t][x].id] = dir[t][x];//rev 对于x来说 他之前边的方向 与 其指向相同
		//而在实际计算中后面对ans 的计算中 其实是取反了的
		//因为对于q[x].id这条边来说 他与新加入的边的关系 相同的
		//若 d==1 则相当于之前的边是指向 x的 
		//若 新边的方向为 0 那么 这一条边的方向不变
		//否则 会改变
		//建议手玩一下 
		a[t][x].id = 0;//把x 之前的边 删了
		x = a[t][x].to;//将x指向 之前边指向的另一个点 
	}
	if(a[t][y].id) {//与 x同理 
		flg = false;
		depend[a[t][y].id] = tot;
		rev[a[t][y].id] = dir[t][y] ^ 1;//这里与x刚好相反 
		a[t][y].id = 0;
		y = a[t][y].to;
	}
	a[t][x].to = y, a[t][y].to = x;//互相 成为 对方到达的点 
	dir[t][x] = 1, dir[t][y] = 0;//钦定方向 默认 从 x -> y的 方向 
	if(flg) a[t][x].id = a[t][y].id = id;//他们的 id 相当于 当前加入的边
	else depend[id] = a[t][x].id = a[t][y].id = tot++;//将他们的id设为新点并且将他们的依靠设为新边
	//至于关于depend的疑惑 实际在 32 和 45行就已经处理过了 
//相当于如果 flag == 0就先当于加边之后有环了所以可以将xy对应的之前的边以及当前加入的边依靠在新边上 
}

void work(int p) {
	int x = p, t = 0;
	vis[x] = 1;
	//t是权值  x是点 a[t][x]相当于 x点在权值t下 对应的唯一一条边 
	//这里相当于从边权为1的边开始走 
	while(a[t][x].id) {
		ans[a[t][x].id] = dir[t][x];//先 将当前边的ans暂且的设为该边本身的方向 
		if(vis[a[t][x].to]) return ;//如果出现环了 就返回 因为继续走下去 他还是个环。
		// 不用担心ans没更新完 因为主函数里面 是一个for 循环 
		vis[a[t][x].to] = true;
		x = a[t][x].to, t ^= 1;
	}
	//这里相当于从边权为2的边开始走 
	x = p, t = 1;
	while(a[t][x].id) {
		ans[a[t][x].id] = dir[t][x] ^ 1;
		if(vis[a[t][x].to])return;
		vis[a[t][x].to] = true;
		x = a[t][x].to, t ^= 1;
	}
}

int main() {
	read(n), read(m), tot = m+1;
	for(int i=1,x,y,z;i<=m;++i) {
		read(x), read(y), read(z);
		add(x, y, i, z-1);//第i条边 对应c-1的权值 连接的是a,b 
	}
	for(int i=0;i<n;++i)
		if(!vis[i]) work(i);
	for(int i=tot;i;--i)
		if(depend[i]) ans[i] = ans[depend[i]] ^ rev[i];//rev 如果为1 就要取反 否则 就不取反 
	for(int i=1;i<=m;++i)
		putchar('0'+ans[i]);
	putchar(10);
	return 0;
}

你可能感兴趣的:(校内模拟,校内模拟)