10.29纪中DAY1_KC看星 & KC的瓷器& 开心小屋

noip2019…counting down three weeks

纪中day1

10.29纪中B组notes

  • KC看星
  • KC的瓷器
  • 开心小屋
蒟蒻的悲惨一天o(╥﹏╥)o

** keys & notes 部分来源于纪中题解**

T1 KC看星

题目描述

jzoj3412 纪中链接(可能失效)
原题链接 codeforces 135B Rectangle and Square
“一闪一闪亮晶晶,满天都是小星星”
Kc吟唱着歌谣,躺在草坪上边想着她边看起了星星。Kc刚刚结识了笛卡尔这位好基友,认为他的坐标系非常神奇。于是他随机地选出了8颗星星,并且给它们标上了坐标。Kc又不甘寂寞,于是思考起一个问题:这八个点能否恰好构成一个正方形和一个矩形呢?

input:
输入文件包括1行16个数,表示8个星星的坐标,坐标绝对值不超过10000。

output:
输出文件第一行是"YES"或者"NO"。表示是否有解。
若有解则第二行依次输出正方形每个顶点的序号。第三行依次输出矩形每个顶点的序号。序号即为输入的顺序。
另外注意:因为kc是一个刁端的人,所以他要求第二行和第三行这八个数要字典序最小。
四点共线不能认为是正方形或矩形

样例:
0 0 10 11 10 0 0 11 1 1 2 2 2 1 1 2

get:
YES
5 6 7 8
1 2 3 4

notes&keys

  1. 枚举4个点,检查是否是正方形
  2. 另四个点是否是矩形,进行判断
  3. (悄悄:直接输出“NO”有30分(* ̄︶ ̄))

求完每两个点之间的距离(平方)后可以直接来个sort

正方形:
四条(短)边(平方)都相等 && 两短边平方之和 = 一长边平方
矩形:
按点坐标(横坐标优先)排序后,三点间向量积=0(详见代码)

按字典序枚举8的全排列,再判断正方形和矩形即可。
正方形判定可以用四边相等,对角线相等。
矩形判定可以用两组对边分别相等,对角线相等。

改了半天终于AC代码

#include<bits/stdc++.h>
using namespace std;
int diss[15];

inline 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;
}


struct node
{
	int x;
	int y;
	
}star[15];	//注意数组要开大一丢丢否则交上去会WA! 

int pand(const struct node a, const struct node b, const struct node c, const struct node d)
{
	int cp = (a.x - b.x) * (c.x - d.x) + (a.y - b.y) * (c.y - d.y);
	if(cp == 0)
		return 1; 
	else 
		return 0;
}//判断向量积 

int dis(const struct node a, const struct node b)
{
	return (a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y);
}

int cmp(const struct node a, const struct node b)
{
	if(a.x == b.x)
		return a.y < b.y;
    else
		return a.x < b.x;
 } //横坐标优先排序点坐标 

int main()
{
	while(~scanf("%d %d", &star[1].x, &star[1].y))
	{
	for(int i = 2; i <= 8; ++i)
	{
		star[i].x = read();
		star[i].y = read();
	} 
	bool vis[15] = {false};
	int flag = 0;
    int cnt;
    struct node t_star[15];
    for(int i = 1; i <= 8; ++i)
    {
    	for(int j = i + 1; j <= 8; ++j)
    	{
    		for(int c = j + 1; c <=8; ++c)
    		{
    			for(int z = c + 1; z <= 8; ++z)	//六条边都遍历一遍 
    			{
    				diss[1] = dis(star[i], star[j]);
    				diss[2] = dis(star[i], star[c]);
    				diss[3] = dis(star[i], star[z]);
    				diss[4] = dis(star[j], star[c]);
    				diss[5] = dis(star[j], star[z]);
    				diss[6] = dis(star[c], star[z]);
    				sort(diss + 1, diss + 7);
    				if(diss[1] == diss[2] && 
						diss[2] == diss[3] && 
						diss[3] == diss[4] && 
						diss[5] == diss[6] && 
						(diss[1] + diss[2] == diss[5]))
    				{
    					flag = 1;
    					vis[i] = true;
						vis[j] = true;
						vis[c] = true;
						vis[z] = true;
    					break;
					}
				}
				if(flag)
					break;
			}
    		if(flag)
				break;
		}
    	if(flag)
			break;	
	}
	if(!flag)
		cout << "NO" << endl;
	else
	{
		int zc1[10], zc2[10], z1 = 1, z2 = 1;
		cnt = 1;
		for(int i = 1; i <= 8; ++i)
		{
			if(!vis[i])
			{
				t_star[cnt++] = star[i];
				zc2[z2++] = i;
			}//未访问过的点:存到zc1中--接下来判断是否为矩形的顶点
			else
				zc1[z1++] = i;//已访问过的点:存到zc2中--正方形的顶点 
		}
		sort(t_star + 1, t_star + cnt + 1, cmp);
		if(pand(t_star[1], t_star[2], t_star[1], t_star[3]) && 
			pand(t_star[1], t_star[3], t_star[3], t_star[4]) && 
			pand(t_star[3], t_star[4], t_star[4], t_star[2]))
		{
			cout << "YES" << endl;
			for(int i = 1; i < z1 - 1; ++i)
				cout << zc1[i] << " ";
			cout << zc1[z1 - 1] << endl;
			for(int i = 1; i < z2 - 1; ++i)
				cout << zc2[i] << " ";
			cout << zc2[z2 - 1] << endl;
		}	//注意开始点是0还是1!! 
		else
			cout << "NO" << endl;
	}
}
	return 0;
}

一点题外话
快给邻桌巨佬调代码整疯了o(╥﹏╥)o
快读优化出毛病爆肝一晚上还是被我爹发现的…-_-||
(此处吹爆我那不学信竞却很全能的老爹哈哈哈o( ̄︶ ̄)o)
不过大佬的思路挺有趣的:
矩形判断方法:
任两条不共顶点的边相等(e.g.1,2,3,4中,12=34,13=24,14=23)
正方形判断方法:
矩形条件下,一个顶点引出的三条边中有任意两条相等
先找一遍正方形,标记已访问的正方形顶点
去除后剩下未访问四个点判断能否组成矩形

T2 KC的瓷器

题目描述

jzoj3413 纪中链接(可能失效)
原题链接 codeforces 148E Porcelain

KC来到了一个盛产瓷器的国度。他来到了一位商人的店铺。在这个店铺中,KC看到了一个有n(1<=n<=100)排的柜子,每排都有一些瓷器,每排不超过100个。那些精美的艺术品使KC一下心动了,决定从N排的商品中买下m(1<=m<=10000)个瓷器。
这个商人看KC的脸上长满了痘子,就像苔藓一样,跟精美的瓷器相比相差太多,认为这么精致的艺术品被这样的人买走艺术价值会大打折扣。商人感到不爽,于是规定每次取商品只能取其中一排的最左边或者最右边那个,想为难KC。
现在KC又获知每个瓷器的价值(用一个不超过100的正整数表示),他希望取出的m个商品的总价值最大。

input:
输入文件的第一行包括两个正整数n,m;
接下来2到n+1行,第i行第一个数表示第i排柜子的商品数量Si,接下来Si个数表示从左到右每个商品的价值。

output:
输出文件只有一个正整数,即m个商品最大的总价值。

example:
输入1:
2 3
3 3 7 2
3 4 1 5

输入2:
1 3
4 4 3 1 2

输出1:
15
样例解释1:
取第一排的最左边两个和第二排的最右边那个。总价直为3+7+5=15;

输出2:
9

对于10%的数据,Si=1,1<=i<=n。
对于另外10%的数据,n=1.

notes&keys

基础的DP题。
预处理take[i] [j],
表示从第i排中取出j个瓷器的最大价值,以及a[i][j]表示第i排前j个瓷器的总价值。
考虑对于每一排取出最左边的k个,那么剩下的j-k个就是最右边的,1<=k<=c。易得:take[i][j] = max ( a[i] [k] + a[i] [c] - a[i] [c - j + k] ),
c表示第i排的瓷器数目。
接下来进行动态规划,
F[i][j]表示从前i排取出j个的最大价值。
F[i][j] = max ( F[i - 1] [j - k] + take[i] [k] )
时间复杂度O(nmc),约10^8

AC代码
代码降维处理了(?),与题解略微不同

#include<bits/stdc++.h>
using namespace std;
const int maxn=10005;
int n, m, cal[maxn], sum[maxn], a[maxn], dp[maxn], tot;

inline 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 main()
{
	n = read();
	m = read();
	tot = 0;
	memset(cal, 0, sizeof(cal));
	for(int i = 1; i <= n; ++i)
	{
		int num = read();
		tot += num;
		memset(dp, 0, sizeof(dp));
		memset(sum, 0, sizeof(sum));
		for(int j = 1; j <= num; ++j)
			a[j]= read(), sum[j] = sum[j - 1] + a[j];
		for(int j = 1; j <= num; ++j)
		{
			for(int c  = 0; c <= j; ++c)
				dp[j] = max(dp[j], sum[c] + sum[num] - sum[num - j + c]);
		}
		int lim = min(tot, m);
		for(int j = lim; j >= 1; --j)
		{
			int limm = min(num, j);
			for(int c = limm; c >= 1; --c)
				cal[j] = max(cal[j], cal[j - c] + dp[c]);
		}
	}
	printf("%d\n", cal[m]);
	return 0;
	
}

T2 开心小屋

题目描述

jzoj3414 纪中链接(可能失效)
原题链接 codeforces 147B smile house

Kc来到开心小屋。开心小屋是用来提升心情的。在这个小屋中有n个房间,一些房间之间有门连通。从房间i到达房间j,心情值可以加上-10000<=Cij<=10000,当然Cij可能是负的。现在kc失恋了,所以他想要知道他是否可以在这个小屋中无限地增加他的心情值,也就是无限地绕着一个环走?
请帮kc求出最小的环需要经过的房间数,来使他的心情无限增加。

input:
第一行给出,1<=n<=300,1<=m<=5000。分别表示房间数及门的数量。
接下来m行,每行四个数:i,j,Cij,Cji

output:
输出文件包括一行,及最小的环需要经过的房间数。
保证不会出现自环及重边。

Sample Input
4 4
1 2 -10 3
1 3 1 -10
2 4 -10 -1
3 4 0 -3

Sample Output
4
样例解释:
1—>3—>4–>2–>1为最小的符合题意的环长度为4.

对30%的数据,n<=10;
对60%的数据,,n<=100;
对100%的数据,n<=300

notes&keys

(然鹅洛谷题解详细得多…)
给出无向图,求:节点数最小的正环长度。
具体代码变量名可能与题解有所不同
因为太蒻我已经被绕晕了
30%:brute force(?)
60%:
floyd预处理出zzz[p][i][j]表示从i到j经过2^p条路径的最长路径。
接着二分答案t
检验答案和预处理类似,只需要考虑二进制位上为1的位置就可以了。
g[z][i][j]表示处理到第z个二进制位上为1的位置从i到j的最长路径
转移方程为:
g[z][i][j]=max(g[z-1][i][k]+zzz[u][k][j])
其中u表示第z个1是在n的二进制表示中的第u位
时间复杂度O(n3log2n),会超时。
100%:
进行二分查找的时候状态g[z][][]会被计算多次。
考虑计算最大的不合法的数,即目标答案减1的数。
从大到小枚举z,
计算出当前答案加上2^z 是否合法
若合法将其舍弃
不合法将2^z加入当前答案
具体实现可以参考标程。-.-
然鹅本蒟蒻的“标程”还没写出来…
洛谷题解

#include <bits/stdc++.h>

using namespace std;
bool b;

int n, m, zz, c, v, w, zzz[10][305][305], ans[2][305][305]; 
bool pand(int u)
{
	b = 0;
	for(int i = 1; i <= n; ++i)
		for(int j = 1; j <= n; ++j)
			{
				//ans[0][i][j] = i;
				if(i == j)
					ans[0][i][j] = 0;
				else
					ans[0][i][j] = -999999999;
					//此处和ans[0][i][j]=i==j ? 0:-999999999;等效
					//三目运算符使用方法注意!!
			}
	for(int k = 0; (1 << k) <= u; ++k)
		if(u &(1 << k))
		{
			b ^= 1;
			for(int i = 1; i <= n; ++i)
				for(int j = 1; j <= n; ++j)
					ans[b][i][j] = -999999999;
			for(int i = 1; i <= n; ++i)
				for(int j = 1; j <= n; ++j)
					for(int z = 1; z <= n; ++z)
					{
						ans[b][i][j] = max(ans[b][i][j], ans[b ^ 1][i][z] + zzz[k][z][j]);
					}
			
		}
		for(int i = 1; i <= n; ++i)
			if(ans[b][i][i] > 0)
				return 1;	
		return 0;//记得看清在括号里还是括号外!!!
}

int main()
{
	scanf("%d%d", &n, &m);
	for(int k = 0; (1 << k) <= n; ++k)
		for(int i = 1; i <= n; ++i)
			for(int j = 1; j <= n; ++j)
			{
				
				if(i == j)
					zzz[k][i][j] = 0;
				else
					zzz[k][i][j] = -999999999;
			}
			
	for(int i = 1; i <= m; ++i)
	{
		scanf("%d%d%d%d", &zz, &c, &v, &w);
		zzz[0][zz][c] = v;
		zzz[0][c][zz] = w;
	}
	
	for(int k = 1; (1 << k) <= n; ++k)
		for(int i = 1; i <= n; ++i)
			for(int j = 1; j <= n; ++j)
				for(int z = 1; z <= n; ++z)
					zzz[k][i][j] = max(zzz[k][i][j], zzz[k - 1][i][z] + zzz[k - 1][z][j]);
	
	int l = 2, r = n + 1;
	while(l < r)
	{
		int mid = (l + r) >> 1;
		if(pand(mid))
			r = mid;
		else
			l = mid + 1;
	}
	printf("%d\n", l % (n + 1));

	return 0;
}

肝了半天终于连抄带写搞出来的代码…
啊my first blog…
谨以此文纪念我逝去的头发(╥╯^╰╥)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值