BJOI2014 2014.8.13

本文分享了三道算法竞赛题目的解题思路与代码实现,包括题目概述、解题方法及个人比赛经验总结。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

今天怒垫一底。要好好总结。


题目:

第一题:

有M个初始题目。我们有另外的N个题目,每个题目都由两个题目Xi,Yi混合而成,我们要知道对于每个题目,他最终由多少个初始题目组成(重复的只记一个)

N <= 1000000,M <= 100000

特殊的地方,我们允许你的程序有一定的误差,如果其中你有95%以上的行的答案和正确答案的误差不超过25%,那么你就可以得到分数。所谓误差不超过25%,即,如果正确答案是X,那么你的答案在[0.8X,1.25X]这个闭区间内。


第二题:

我们有N个点,Q个操作。

对于A操作,读入x,y,表示x,y之间连一条边。(数据保证始终为一个树)

对于Q操作,读入x,y,询问有多少条简单路径经过x,y这条边(保证x,y这条边曾经出现)

N <= 100000,Q <= 100000

第三题:

我们给定一个图,每个顶点都有一个字符(0~9,(,),+,-,*,/),给定一个路径长度,在起点与终点不限定的情况下,让你求出有多少条路径,其顶点组成一个合法表达式。(注意,你要处理各种情况,比如数字不能有多余的前导0,减号只有前面没有运算符或数字的时候才可以当成负号,括号可以任意添加(但不能有空括号),0可以做除数(我们只考虑文法而不考虑语意),加号不能当正号。)


比赛时的情况:

第一题一眼看上去没有什么思路,发现60%分(N <= 100000,M <= 10000)的可以直接用压位碾过去,就直接不想了。

第二题,一看就是数据结构题。我一开始考虑离线,开线段树维护连了区间的边的联通情况。想了很久都没有解决并查集的区间加法(事实上这个做不了).

于是我就直接跳进了在线的大坑,一直在想LCT怎么做。

难点在于连边的时候我们需要旋根,儿子个数就会发生改变!!

我是这样想的,考虑旋根对答案的影响只有对他到根的节点。于是我们就可以分情况考虑(事实上非常的繁琐,我反正是没调出来)

第三题,一眼看上去觉得不会。然后就不去想了。。。没有看到各种各样的限制条件。-_-  -_- -_-其实这道题水的不得鸟。。。。


讲一下题解吧。一样先按难度排序(3,2,1)


第三题,注意到我们需要注意的只是他各种各样的非法条件。而我们其实能列举出来。

我们用*代替运算符,1代替非0的数字。

非法条件其实就只是:

* *                     *)               (*[且* 不为-],          )1            1(               01        ()       )(


然后我们直接DP就好了。。。。


第二题:

难点其实就在于我们加边的时候树的形态不确定。

那就先把整棵树建出来!!!!!!

建好树后,我们再作操作。

对于Add(x,y)

我们可以找出靠下的那个点,设为y

我们可以倍增出当前连到的深度最小的点u

那么对于x->u的点,深度比他大的点的个数就+上y的值!树链剖分就可以暴力过。


第一题:

果然是涉及面广(偏)、考察深入(怪)、思维强度大(难)的阿米巴和小强出的题。

来看一下我们60分的做法。

     压位。

我们考虑怎么来优化。

我们对于M个初始题目random一个0~rand_max的值。

设一个值T,表示我们对于每个题目只保留T个初始题目。

对于合并操作(X+Y->Z):

我们将X,Y保留的值归并排序(并且顺便去重)

   我们Z要保留的值就是前T小的值。

   设P为第T小的值。

   然后对于Z的答案就是T * rand_max / P.

   我们对于一个点随机多次取答案的平均值就能尽量减小误差了。

合(da)理(dan)证(cai)明(ce)

因为是random出来的值,所以初始题目的值是在0~rand_max随机分布的。

而我们在K个数中取到最小的T个数的概率为T/K

这个概率就等于P/rand_max(放大与缩小?)


暴露的问题:

今天暴露的问题还是十分严重的。

对于打不出来的程序要及时取舍。注意分析时间。

题目必须要读完。不能一眼过。要每一道题都分析,剖析,化繁为简。


贴代码。


第一题

#include
#include
#include
#include

using namespace std;

const int MAXN = 1000005;

long double Total[MAXN];
int T,N,M,X[MAXN],Inc[MAXN][25],Y[MAXN];

bool cmp(double a,double b) {return a > b;}

void Merge(int *a,int *b,int *c)
{
	int l = 1,r = 1,tot = 0;
	while ((l <= b[0]) && (r <= c[0]) && (tot + 1 <= T))
	if (b[l] < c[r]) a[++ tot] = b[l ++]; else if (b[l] > c[r]) a[++ tot] = c[r ++]; else
	a[++ tot] = b[l ++],r ++;
	while (l <= b[0] && (tot + 1 <= T)) a[++ tot] = b[l ++];
	while (r <= c[0] && (tot + 1 <= T)) a[++ tot] = c[r ++];
	a[0] = tot;
}

int main()
{
	srand((unsigned)time(0));
	scanf("%d %d", &N, &M);
	for(int i = M + 1;i <= N;i ++) scanf("%d %d",&X[i],&Y[i]);
	T = 20; 
	int C = 10000000;
	int Time = C / N;
	for(int i = 1;i <= Time;i ++)
	{
		for(int j = 1;j <= M;j ++) Inc[j][1] = rand() + 1,Inc[j][0] = 1;
		for(int j = M + 1;j <= N;j ++)
		{
			Merge(Inc[j],Inc[X[j]],Inc[Y[j]]);
			int val = (Inc[j][0] < T) ? Inc[j][0] : (Inc[j][0] * ((double)RAND_MAX / (Inc[j][Inc[j][0]] + 1)));
			Total[j] += val;
		}
	}
	for(int i = M + 1;i <= N;i ++) printf("%d\n", (int)(Total[i] / Time + 0.001));
}

第二题

#include
#include
#include
#include
#include

using namespace std;

typedef pair P;

const int MAXN = 200005;

struct Node
{
	int add,value;
}T[MAXN * 4];

bool bz[MAXN];
int Next[MAXN],To[MAXN],tot;
int Link[MAXN][3],Final[MAXN],Pre[MAXN],Ord[MAXN],H[MAXN],Fa[MAXN][20],Son[MAXN],N,M,Q,cnt;
int Stack[MAXN];

void read(int &x)
{
	char c;
	while (c = getchar(),c < '0' || c > '9');
	x = c - 48;
	while (c = getchar(),c >= '0' && c <= '9') x = x * 10 + c - 48;
}
 
void link(int x,int y) 
{To[++ tot] = y,Next[tot] = Final[x],Final[x] = tot;}
 
void Get_Son(int Now)
{
	Son[Now] = 1;bz[Now] = 1;
	for(int i = Final[Now];i;i = Next[i])
	if (!bz[To[i]]) Fa[To[i]][0] = Now,Get_Son(To[i]),Son[Now] += Son[To[i]];
} 
 
void Get_Height(int Now,int Fa,int Pre)
{
	Ord[Now] = ++ cnt,H[Now] = Fa;
	int h = -1;
	for(int i = Final[Now];i;i = Next[i])
	if ((To[i] != Pre) && ((h == -1) || (Son[To[i]] > Son[h]))) h = To[i];
	if (h == -1) return;
	Get_Height(h,Fa,Now);
	for(int i = Final[Now];i;i = Next[i])
	if ((To[i] != Pre) && To[i] != h) Get_Height(To[i],To[i],Now);
} 
 
int get(int a) {return Stack[a] == a ? a : Stack[a] = get(Stack[a]);} 
 
int To_Search(int y)
{
	int v = get(y);
	for(int j = 17;j + 1;j --)
	if (v == get(Fa[y][j])) 
		y = Fa[y][j];
	return y;
} 

void Build(int l,int r,int jd)
{
	if (l == r) T[jd].value = 1; else
	{
		int mid = (l + r) / 2;
		Build(l,mid,jd * 2),Build(mid + 1,r,jd * 2 + 1);
	}
}
 
void Label(int jd,int ad) {T[jd].value += ad,T[jd].add += ad;} 
 
int Find(int l,int r,int p,int jd)
{
	if (l == r) return T[jd].value; else
	{
		int mid = (l + r) / 2;
		if (T[jd].add) Label(jd * 2,T[jd].add),Label(jd * 2 + 1,T[jd].add),T[jd].add = 0;
		if (p <= mid) return Find(l,mid,p,jd * 2); else return Find(mid + 1,r,p,jd * 2 + 1);
	}
} 

void Change(int l,int r,int x,int y,int v,int jd)
{
	if (l == x && r == y) T[jd].value += v,T[jd].add += v; else
	{
		int mid = (l + r) / 2;
		if (T[jd].add) Label(jd * 2,T[jd].add),Label(jd * 2 + 1,T[jd].add),T[jd].add = 0;
		if (y <= mid) Change(l,mid,x,y,v,jd * 2); else if (x > mid) Change(mid + 1,r,x,y,v,jd * 2 + 1); else
		Change(l,mid,x,mid,v,jd * 2),Change(mid + 1,r,mid + 1,y,v,jd * 2 + 1);
	}
}
 
int main()
{
	read(N),read(M);
	for(int i = 1;i <= N;i ++) Stack[i] = i;
	for(int i = 1;i <= M;i ++)
	{
		char c;while (c = getchar(),c < 'A' || c > 'Z');
		int x,y;read(x),read(y);
		Link[i][1] = x,Link[i][2] = y;
		if (c == 'A') Link[i][0] = 1,link(x,y),link(y,x);
	}
	for(int i = 1;i <= N;i ++) 
	if (!bz[i])
	{
		Get_Son(i);
		Get_Height(i,i,0);
	}
	for(int i = 1;i <= 17;i ++)
		for(int j = 1;j <= N;j ++)
			Fa[j][i] = Fa[Fa[j][i - 1]][i - 1];
	Build(1,N,1);
	for(int i = 1;i <= M;i ++)
	{
		int x = Link[i][1],y = Link[i][2];
		if (Fa[x][0] == y) swap(x,y);
		if (Link[i][0])
		{
			Stack[get(y)] = get(x);
			int Up = Ord[To_Search(y)],Vnow = Find(1,N,Ord[y],1);
			y = Fa[y][0];
			while (Ord[y] >= Up)
			{
				if (Ord[H[y]] >= Up) Change(1,N,Ord[H[y]],Ord[y],Vnow,1); else
					Change(1,N,Up,Ord[y],Vnow,1);
				y = Fa[H[y]][0];
			}
		} else
		{
			int Up = Ord[To_Search(y)],Tmp = Find(1,N,Up,1);
			int Tmp1 = Find(1,N,Ord[y],1);
			printf("%lld\n",(long long)(Tmp - Tmp1) * Tmp1);
		}
	}
	return 0;
}

第三题
#include
#include

using namespace std;

const int MAXN = 40,Mo = 1000000007;

int link[MAXN][MAXN],N,M,K;
char s[MAXN];
long long f[MAXN][MAXN][MAXN][2];

bool Num(int i)
{
	return (s[i] >= '0' && s[i] <= '9');
}

bool Br(int i)
{
	return (s[i] == '*' || s[i] == '/' || s[i] == '+' || s[i] == '-');
}

int main()
{
	scanf("%d %d %d", &N, &M, &K);
	scanf("%s", s + 1);
	for(int i = 1;i <= M;i ++)
	{
		int x,y;scanf("%d %d", &x, &y),link[x][y] = link[y][x] = 1;
	}
	for(int i = 1;i <= N;i ++)
	{
		if (Num(i)) f[1][i][0][s[i] == '0'] = 1;
		if (s[i] == '(') f[1][i][1][0] = 1;
		if (s[i] == '-') f[1][i][0][0] = 1;
	}
	for(int step = 1;step < K;step ++)
		for(int i = 1;i <= N;i ++)
			for(int br = 0;br <= N / 2;br ++)
				for(int d = 0;d < 2;d ++)
				if (f[step][i][br][d])
					for(int j = 1;j <= N;j ++)
					if (link[i][j])
					{
						if (Br(i) && s[j] == ')') continue;
						if (s[i] == '(' && (Br(j) && s[j] != '-')) continue;
						if (s[i] == ')' && Num(j)) continue;
						if (Num(i) && s[j] == '(') continue;
						if (d && Num(j)) continue;
						if (s[i] == '(' && s[j] == ')') continue;
						if (s[i] == ')' && s[j] == '(') continue;
						if (!br && s[j] == ')') continue;
						if (Br(i) && Br(j)) continue;
						if (Num(j))
						{
							if (s[j] != '0' || Num(i)) (f[step + 1][j][br][0] += f[step][i][br][d]) %= Mo; else
								(f[step + 1][j][br][1] += f[step][i][br][d]) %= Mo;
						} else
						if (s[j] == '(') (f[step + 1][j][br + 1][0] += f[step][i][br][d]) %= Mo; else 
						if (s[j] == ')') (f[step + 1][j][br - 1][0] += f[step][i][br][d]) %= Mo; else
						if (Br(j))(f[step + 1][j][br][0] += f[step][i][br][d]) %= Mo;
					}
	long long Ans = 0;
	for(int i = 1;i <= N;i ++)
	if (Num(i) || s[i] == ')') 
		for(int d = 0;d < 2;d ++) (Ans += f[K][i][0][d]) %= Mo;
	printf("%lld\n",Ans);
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值