noip55

T1

考场想法:一看就是个dp题,于是写了个两维的dp,一测样例,发现假了,没有考虑后边的比赛没有选所造成的负贡献,发现两维的不好搞,想了一下,那干脆直接一维,设 \(dp_{i}\) 表示最后一场为i时的最大贡献,后边不选所造成的负贡献,加加减减就出来了,然后我就脑抽了,没有打就觉得计算负贡献不好搞,实际就是减去一个求和公式。干脆打了个暴力走人。

30pts:直接 \(n^{2}\) dp。

dp部分就是考场上想的:设 \(dp_{i}\) 表示以 \(i\) 为最后一场时的最大的愉悦值,转移方程也挺简单:

\[dp_{i} = \max\{dp_{j}+a_{i}-\tbinom{i-j}{2}\} \]

后边的式子就是在减去 \(i,j\) 两场之间的比赛没选所造成的负贡献,或者直接用等差数列求和都行我是zz

60pts:多了 \(a_{i}\) 随机的部分分,把枚举j的边界改成\(\max(0,i-3000)\) 即可,改小了wa,改大了TLE,并不知道zjx考场上怎么试出来的。

100pts:线段树/cdq分治维护单调栈。

T2

考场想法:没有什么想法。

20pts:暴力乱搞...

100pts:不会...

T3

考场想法:数学题,那就尝试打表找规律,发现不好打,于是干脆丢了个状压拿30pts走人。

考后wsn靠打表A了此题

30pts:暴力乱搞。

70pts:不会...

100pts:不会...

\(n=n-k+m,k=m\) ,那么答案就是:

\[ \sum_{i=1}^{k}\tbinom{n-i}{k-i+2}+\sum_{i=2}^{n}\tbinom{n-i+1}{k-2}\times (i-\tbinom{i-1}{2}-1) \]

Code
#include<cstdio>
#include<cctype>
#define re register
#define MAX 1000003
#define int long long
const int mod = 1e9+7;
namespace some
{
	struct stream
	{
		template<typename type>inline stream &operator >>(type &s)
		{
			bool w=0; s=0; char ch=getchar();
			while(!isdigit(ch)){ w|=ch=='-'; ch=getchar(); }
			while(isdigit(ch)){ s=(s<<1)+(s<<3)+(ch^48); ch=getchar(); }
			return s=w?-s:s,*this;
		}
	}cin;
	auto fd = [](int a,int b) -> int { return b-mod+a>=0?b-mod+a:a+b; };
	auto debug = []() { printf("fuck\n"); }; auto line = []() { printf("\n"); };
	auto begin = []() { printf("begin\n"); }; auto end = []() { printf("end\n"); };
}using namespace some;
namespace OMA
{
	int n,k,m,ans;
	int fac[MAX],inv[MAX];
	auto quickpow = [](int a,int b,int res = 1) -> int
	{
		while(b)
		{
			if(b&1)
			{ res = res*a%mod; }
			a = a*a%mod,b >>= 1;
		}
		return res;
	};
	auto C(int n,int m) -> int { return (n<m||n<0||m<0)?0:fac[n]*inv[n-m]%mod*inv[m]%mod; };
	auto main = []() -> signed
	{
		freopen("perm.in","r",stdin); freopen("perm.out","w",stdout);
		cin >> n >> k >> m;
		fac[0] = inv[0] = 1,n -= k-m,k = m;
		if(k==1)
		{ printf("%lld\n",n-1); return 0; }
		for(re int i=1; i<=n; i++)
		{ fac[i] = fac[i-1]*i%mod; }
		inv[n] = quickpow(fac[n],mod-2);
		for(re int i=n-1; i; i--)
		{ inv[i] = inv[i+1]*(i+1)%mod; }
		for(re int i=1; i<=k; i++)
		{ ans = fd(ans,C(n-i,k-i+2)); }
		for(re int i=2; i<=n; i++)
		{ ans = fd(ans,C(n-i-1,k-2)*(i-C(i-1,2)-1+mod)%mod); }
		printf("%lld\n",ans);
		return 0;
	};
}
signed main()
{ return OMA::main(); }

T4

考试想法:直接生成树乱搞,就是个大板题,结果码了一会儿发现先算每条边的w显然是错的,于是就去骗分了,结果并查集写错了+考虑情况少了,实数的20pts没拿着,暴力加了个判断n的大小,少拿5pts。

没有直接输出”xxx“这种的,打暴力骗分就不要加范围特判了,容易出锅....

15pts:暴力乱搞。

35pts:多了实数的分,那直接跑kruskal即可,注意负数取绝对值可能比正数大,所以从大到小排序跑一遍,从小到大排序跑一遍。

100pts:

如果已知最后求出的复数的单位向量,那么要使得生成树边权和的模长最大,只需使各复数在该方向向量上的投影和最大。这样的和我们只需按照投影排序,跑最大生成树即可解决。

然而单位向量有很多,枚举是不太可行的。其实枚举非常可做

考虑通过两个复数在某个单位向量上的投影相等,来找出该单位向量。枚举复数对,这样就有了很多个单位向量,都存下来,排个序,然后就把坐标系划分成了好几部分,在空白区间内,没有别的向量再来卡,那么这个区间内,所有复数的投影的相对大小关系是不会变的。

再由kruskal可知:我们只关心边权的相对大小,并不在乎具体数值。

所以只需枚举对应的空白部分,在这里边瞎取一个,算出所有投影,排序,跑最大生成树,找出最优答案即可。

我的代码没给单位向量排序,是假的,需要给所有求出来的单位向量排个序,再去枚举空白部分。

本题综合考察了高考数学当中复数相关的知识,对选手综合素养要求较高,编不下去了

Code
#include<cmath>
#include<cstdio>
#include<cctype>
#include<algorithm>
const int N = 53;
const int M = 203;
#define re register
using std::sort;
const double pi = acos(-1);
namespace some
{
	struct stream
	{
		template<typename type>inline stream &operator >>(type &s)
		{
			bool w=0; s=0; char ch=getchar();
			while(!isdigit(ch)){ w|=ch=='-'; ch=getchar(); }
			while(isdigit(ch)){ s=(s<<1)+(s<<3)+(ch^48); ch=getchar(); }
			return s=w?-s:s,*this;
		}
	}cin;
	int n,m;
	double val,l,r,mid,ans;
	template<typename type>inline type max(type a,type b){ return a>b?a:b; }
}using namespace some;
namespace MST
{
	int fa[N];
	int vala,valb;
	struct EDGE
	{
		int u,v,a,b;
		double w;
	}path[M],p[M];
	auto cmp1 = [](const EDGE &x,const EDGE &y) -> bool { return x.w>y.w; };
	auto cmp2 = [](const EDGE &x,const EDGE &y) -> bool { return x.w<y.w; };
	int find(int x) { return x!=fa[x]?fa[x] = find(fa[x]):fa[x]; }
	auto merge = [](int x,int y) { int r1 = find(x),r2 = find(y); fa[r2] = r1; };
	auto kruskal = []() -> void
	{
		vala = valb = 0;
		for(re int i=1; i<=n; i++)
		{ fa[i] = i; }
		for(re int i=1,u,v; i<=m; i++)
		{
			u = path[i].u,v = path[i].v;
			if(find(u)!=find(v)) { merge(u,v); vala += path[i].a,valb += path[i].b; }
		}
		ans = max(ans,sqrt(vala*vala+valb*valb));
		vala = valb = 0;
		for(re int i=1; i<=n; i++)
		{ fa[i] = i; }
		for(re int i=m,u,v; i; i--)
		{
			u = path[i].u,v = path[i].v;
			if(find(u)!=find(v)){ merge(u,v); vala += path[i].a,valb += path[i].b; }
		}
		ans = max(ans,sqrt(vala*vala+valb*valb));
	};
}using namespace MST;
namespace OMA
{
	auto main = []() -> signed
	{
		freopen("mst.in","r",stdin);
		freopen("mst.out","w",stdout);
		cin >> n >> m;
		for(re int i=1,u,v,a,b; i<=m; i++) { p[i] =  path[i] = (EDGE){(cin >> u,u),(cin >> v,v),(cin >> a,a),(cin >> b,b)}; }
		for(re int i=1; i<=m; i++)
		{
			for(re int j=i+1; j<=m; j++)
			{
				val = (1.0*(p[i].a-p[j].a))/(1.0*(p[j].b-p[i].b));
				l = atan(val),r = l+pi,mid = (l+r)/2.0;
				//printf("l=%0.6lf r=%0.6lf\n",l,r);
				for(re int k=1; k<=m; k++) { path[k].w = 1.0*path[k].a*cos(mid)+1.0*path[k].b*sin(mid); }
				sort(path+1,path+1+m,cmp1); kruskal();
				//sort(path+1,path+1+m,cmp2); kruskal();
			}
		}
		printf("%0.6lf\n",ans);
		return 0;
	};
}
signed main()
{ return OMA::main(); }

话说直接枚举角都能过,跑的还奇快,此题数据简直牛马。

posted @ 2021-09-17 20:00  -OMA-  阅读(98)  评论(0编辑  收藏  举报
浏览器标题切换
浏览器标题切换end