广工2016校赛决赛

重现补的题目。

 

Problem A: Krito的讨伐


思路:不要求一次性杀光一个节点里面的所有怪物。 所以我们能够用一个优先队列。优先去杀那些我们当前能够挑战的。然后注意下处理一个房间可能有多个怪物或者无怪物。当我们杀完第x个房间的怪物时候。那么就把x的下一层的怪物增加队列,假设x的下一层出现了空房间[即房间不存在怪物],那么再把该房间当做新的x,继续增加新x的下一层直到出现了有怪物的房间位置。

#define _CRT_SECURE_NO_DEPRECATE
#include<iostream>
#include<stdio.h>
#include<algorithm>
#include<vector>
#include<queue>
#include<functional>
#include<cstring>
#include<string>
using namespace std;
typedef long long int LL;
const int MAXN = 1005;
struct Node
{//怪物
	int id, def, add_atk; //所在房间,防御值。击杀额外获得的攻击力
};
struct cmp
{//按防御值最低而且额外值最高排序[优先队列]
	bool operator ()(const Node &a, const Node &b)
	{
		if (a.def == b.def)
		{
			return a.add_atk < b.add_atk;
		}
		return a.def > b.def;
	}
};
bool cmp0(Node a, Node b)
{ //按防御值最低而且额外值最高排序[预处理第0号房间]
	if (a.def == b.def)
	{
		return a.add_atk > b.add_atk;
	}
	return a.def < b.def;
}
vector<Node>GW[MAXN]; //GW[i]:第i个房间的怪物情况
vector<int>G[MAXN]; //图。保存树的结构
int n, m, t, atk, num[MAXN], vis[MAXN];
//点数。怪物数,例子数。每一个房间的怪物数。訪问数组
bool bfs()
{
	priority_queue<Node, vector<Node>, cmp>Q; //怪物队列
	queue<int>inq; //扩展队列,保存x,即保存能扩展下一层的房间号
	inq.push(0); vis[0] = 1;
	while (!inq.empty())
	{
		int top = inq.front(); inq.pop();
		if (GW[top].size()) //非空房间
		{
			for (int i = 0; i < GW[top].size(); i++)
			{ //增加此房间的怪物到怪物队列
				Q.push(GW[top][i]);
			}
		}
		else //此房间为空房间
		{
			for (int i = 0; i < G[top].size(); i++)
			{ //继续想inq队列增加下一层结点。继续扩展
				if (vis[G[top][i]] == 0)
				{
					vis[G[top][i]] = 1;
					inq.push(G[top][i]);
				}
			}
		}
	}
	while (!Q.empty())
	{
		Node front = Q.top(); Q.pop();
		if (atk <= front.def) //当前最优都无法击杀。那么后面肯定击杀不了。

{ return false; } atk += front.add_atk; num[front.id]--; if (num[front.id] == 0) //此房间的怪物已经击杀完或者是空房间 { for (int i = 0; i < G[front.id].size(); i++) //同上处理 { if (vis[G[front.id][i]] == 0) { vis[G[front.id][i]] = 1; inq.push(G[front.id][i]); } } while (!inq.empty()) { int top = inq.front(); inq.pop(); if (GW[top].size()) { for (int i = 0; i < GW[top].size(); i++) { Q.push(GW[top][i]); } } else { for (int i = 0; i < G[top].size(); i++) { if (vis[G[top][i]] == 0) { vis[G[top][i]] = 1; inq.push(G[top][i]); } } } } } } return true; //所有怪物击杀完毕 } void init() { memset(num, 0, sizeof(num)); memset(vis, 0, sizeof(vis)); for (int i = 0; i <= n; i++) { G[i].clear(); GW[i].clear(); } } int main() { scanf("%d", &t); while (t--) { init(); scanf("%d%d", &n, &m); for (int i = 0; i < n - 1; i++) { int u, v; scanf("%d%d", &u, &v); G[u].push_back(v); G[v].push_back(u); } scanf("%d", &atk); for (int i = 0; i < m; i++) { Node temp; scanf("%d%d%d", &temp.id, &temp.def, &temp.add_atk); GW[temp.id].push_back(temp); num[temp.id]++; } if (bfs()) { printf("Oh yes.\n"); } else { printf("Good Good Study,Day Day Up.\n"); } } return 0; }


Problem B: Sward Art Online

思路:简单分组背包[事实上分完组能够用贪心来求],一共同拥有4种装备。各自是头盔。首饰,单手武器,双杀武器。

那么我们能够把这些装备分成2组,即防具和武器。由于每组装备仅仅能选一个,那么能够这么定义,防具组:仅仅选头盔,仅仅选首饰,头盔和首饰组合[注意部分有buff加成]。 武器:仅仅选一个单手武器。 仅仅选2个单手武器[组成双手武器],仅仅选双手武器。

   然后就是分成2组简单的01背包。

之后就是01背包的求解。    事实上能够不用背包:由于仅仅有2组。

那么我们能够在限有的金币下,在x=仅仅选防具的最大攻击力,在y=仅仅选武器的最大攻击力,在z=选防具和武器搭配的最大攻击力.   终于结果就是max(x,y,z)

#define _CRT_SECURE_NO_DEPRECATE
#include<iostream>
#include<cstring>
#include<string>
#include<algorithm>
#include<stdio.h>
#include<set>
#include<vector>
using namespace std;
typedef long long int LL;
const int MAXN = 10000 + 5;
const int MAXZ = 105;
struct Node
{
	int w, v; //价格,攻击力
	int id, buff; //是否有buff加成
	Node(int b = 0, int c = 0, int d = -1, int e = 0) :w(b), v(c), id(d), buff(c){};
	void init(int a = 0, int b = 0, int c = -1, int d = -1)
	{
		w = a; v = b; id = c; buff = d;
	}
};
Node Tk[MAXZ], Ss[MAXZ], Dw[MAXZ], Sw[MAXZ], TS[3][MAXN];
//头盔。首饰,单手武器,双手武器。TS[1]:防具组,TS[2]:武器组
int main()
{
	int t;
	scanf("%d", &t);
	while (t--)
	{
		int m, a, b, c, d, ts = 0, ts2 = 0;
		int wei, val, Buff, Id;
		scanf("%d%d%d%d%d", &m, &a, &b, &c, &d);
		for (int i = 0; i<a; i++) //头盔
		{
			scanf("%d%d", &wei, &val);
			Node temp(wei, val, 0, 0);
			Tk[i] = temp;
			TS[1][ts++] = temp; //防具组:仅仅选一个头盔
		}
		for (int i = 0; i<b; i++) //首饰
		{
			scanf("%d%d%d%d", &wei, &val, &Id, &Buff);
			Node temp(Node(wei, val, Id, Buff));
			Ss[i] = temp;
			TS[1][ts++] = temp;  //防具组:仅仅选一个首饰
			if (Id == -1 || Buff <= 0)
			{
				continue;
			}
			temp.init(Ss[i].w + Tk[Id].w, Ss[i].v + Tk[Id].v + Buff, 0, 0);
			TS[1][ts++] = temp; //防具组:有buff加成的头盔+首饰
		}
		for (int i = 0; i<a; i++) //防具组:头盔+首饰
		{
			for (int j = 0; j<b; j++)
			{
				Node temp(Node(Tk[i].w + Ss[j].w, Tk[i].v + Ss[j].v, 0, 0));
				TS[1][ts++] = temp;
			}
		}
		for (int i = 0; i<c; i++) //单手武器
		{
			scanf("%d%d", &wei, &val);
			Node temp(wei, val, 0, 0);
			TS[2][ts2++] = temp; //武器组:仅仅选一个单手武器
			Dw[i] = temp;
		}
		for (int i = 0; i<c; i++) //武器组:一个单手武器+一个单手武器=双手武器
		{
			for (int j = i + 1; j<c; j++)//由于每种仅仅有一个,所以j要从i+1開始
			{
				Node temp(Dw[i].w + Dw[j].w, Dw[i].v + Dw[j].v);
				TS[2][ts2++] = temp;
			}
		}
		for (int i = 0; i<d; i++) //双手武器
		{
			scanf("%d%d", &wei, &val);
			Node temp(wei, val, 0, 0);
			TS[2][ts2++] = temp; //武器组:仅仅选一个双手武器。
		}
		//贪心求法:
		LL ans = 0; 
		for (int i = 0; i<ts; i++)
		{//在限有的金币下,x=仅仅选防具的最大攻击力
			if (TS[1][i].w <= m) //在限有的金币下
			{
				ans = max(ans, 1LL * TS[1][i].v);
			}
		}
		for (int i = 0; i<ts2; i++)
		{ //在限有的金币下。y=仅仅选武器的最大攻击力
			if (TS[2][i].w <= m) //在限有的金币下
			{
				ans = max(ans, 1LL * TS[2][i].v);
			}
		}
		for (int i = 0; i<ts; i++)
		{//z=选防具和武器搭配的最大攻击力
			for (int j = 0; j<ts2; j++)
			{
				if (TS[1][i].w + TS[2][j].w <= m) //在限有的金币下
				{
					ans = max(ans, 1LL * (TS[1][i].v + TS[2][j].v));
				}
			}
		}
		printf("%lld\n", ans); //ans=max(x,y,z)

		//////////////////////////分组背包求法//////////////////////////////////
		int dp[3][MAXN];
		memset(dp, 0, sizeof(dp));
		for (int i = 1; i <= 2; i++)
		{
			for (int k = 0; k <= m; k++)
			{
				dp[i][k] = dp[i - 1][k];
			}
			for (int k = 0; k<(i == 1 ? ts : ts2); k++)
			{
				for (int j = m; j >= TS[i][k].w; j--)
				{
					dp[i][j] = max(dp[i][j], dp[i - 1][j - TS[i][k].w] + TS[i][k].v);
				}
			}
		}
		printf("%d\n", dp[2][m]);
	}
	return 0;
}


Problem C: wintermelon的魔界寻路之旅

思路:先求出最短路长度,然后dfs记忆化搜索满足最短路的路径个数。 我们令D[i][j]为从位置(1,1)到位置(i,j)的最短路长度+相应关于对角线对称的长度,那么D[X][Y](X+Y==n+1)即到达对角线的时候就是真实的从(1,1)到终点(n,n)的长度。

然后就是从对角线沿着最短路走到(1,1)的路径计数了。

详细看代码吧。

#define _CRT_SECURE_NO_DEPRECATE
#include<iostream>
#include<stdio.h>
#include<algorithm>
#include<string>
#include<cstring>
#include<stack>
#include<queue>
using namespace std;
typedef long long int LL;
const int MAXN = 100 + 5;
const int INF = 0x3f3f3f3f;
const int MOD = 1000000009;
int D[MAXN][MAXN], g[MAXN][MAXN];
//D:起点到其它点的最短路距离
int n, minval, dist[4][2] = { 0, 1, 0, -1, 1, 0, -1, 0 };
//minval:起点到终点的最短路距离,dist:方向向量
struct Node
{
	int x, y;
	int val;
};
struct cmp
{
	bool operator()(const Node &a, const Node &b)
	{
		return a.val > b.val;
	}
};
bool check(int x, int y)//是否越界
{
	return x >= 1 && x <= n&&y >= 1 && y <= n;
}
void bfs(Node start)
{
	priority_queue<Node, vector<Node>, cmp>Q;
	memset(D, -1, sizeof(D));
	Q.push(start);
	D[start.x][start.y] = start.val;
	while (!Q.empty())
	{
		Node t = Q.top(); Q.pop();
		Node next;
		for (int i = 0; i < 4; i++)
		{
			next.x = t.x + dist[i][0];
			next.y = t.y + dist[i][1];
			if (check(next.x, next.y) && D[next.x][next.y] == -1 && next.x + next.y <= n + 1)
			{ //g[next.x][next.y]为当前值,g[n - next.y + 1][n - next.x + 1]为对角线对称点值
				next.val = t.val + g[next.x][next.y] + (next.x + next.y == n + 1 ?

0 : g[n - next.y + 1][n - next.x + 1]); D[next.x][next.y] = next.val; Q.push(next); } } } minval = INF; for (int i = 1; i <= n; i++)//找到最短路 { minval = min(minval, D[i][n + 1 - i]); } } int dp[MAXN][MAXN]; int dfs(int x, int y) { if (dp[x][y] != -1)//记忆化搜索 { return dp[x][y]; } int nextx, nexty, cnt = 0; for (int i = 0; i < 4; i++) { nextx = x + dist[i][0]; nexty = y + dist[i][1]; if (check(nextx, nexty) && nextx + nexty <= n + 1 && D[nextx][nexty] + g[x][y] + (x + y == n + 1 ? 0 : g[n - y + 1][n - x + 1]) == D[x][y]) { cnt = (cnt + dfs(nextx, nexty)) % MOD; } } dp[x][y] = cnt%MOD; return dp[x][y]; } int main() { int t; scanf("%d", &t); while (t--) { scanf("%d", &n); for (int i = 1; i <= n; i++) { for (int j = 1; j <= n; j++) { scanf("%d", &g[i][j]); } } Node start; start.x = 1; start.y = 1, start.val = g[1][1] + g[n][n]; //起点。 bfs(start); //求(1,1)到其它点的最短路距离 /*for (int i = 1; i <= n; i++) //Debug { for (int j = 1; j <= n; j++) { printf("%d ", D[i][j]); } printf("\n"); }*/ memset(dp, -1, sizeof(dp)); LL ans = 0; dp[1][1] = 1; for (int i = 1; i <= n; i++) { if (D[i][n + 1 - i] == minval) { ans = (ans + dfs(i, n + 1 - i)) % MOD; } } printf("%lld\n", ans); } return 0; }


Problem D: 二叉树的中序遍历

思路:仅仅要出现两个连续的##就输出no,否则就输出yes。证明略,仅仅有多画几个就能找到规律

#include<iostream>
#include<cstring>
#include<string>
#include<algorithm>
#include<stdio.h>
#include<set>
using namespace std;
typedef long long int LL;
const int MAXN=100000+5;
char str[MAXN];
int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%s",str); bool flag=true;
        for(int i=0;i<strlen(str)-1;i++)
        {
            if(str[i]=='#'&&str[i+1]=='#')
            {
                flag=false;
                break;
            }
        }
        printf(flag?"yes\n":"no\n");
    }
    return 0;
}


Problem E: 积木积水

思路:能够发现能装到水的当且仅当形状呈u型,那么能够用一个栈保存一个高度递减的序列,当前高度大于栈顶高度,则能够装水。   关于装水能够用相似扫描线的思想。  该题也有dp的做法。

#define _CRT_SECURE_NO_DEPRECATE
#include<iostream>
#include<stdio.h>
#include<algorithm>
#include<string>
#include<cstring>
#include<stack>
#include<queue>
using namespace std;
typedef long long int LL;
const int MAXN = 1000000 + 5;
struct Node
{
	int H;
	int id;
}A[MAXN];
void solve(int n)
{
	LL ans = 0;
	stack<Node>st;
	for (int i = 0; i < n; i++)
	{
		if (st.empty())
		{
			st.push(A[i]);
		}
		else
		{
			LL top_h = 0;
			LL cnt = 0, tot = 0;
			while (!st.empty())
			{
				Node tmp = st.top();
				top_h = tmp.H;
				if (A[i].H > tmp.H)
				{
					cnt += 1LL * (A[i].id - tmp.id);
					tot += 1LL * (A[i].id - tmp.id)*tmp.H;
					A[i].id = tmp.id;
					st.pop();
				}
				else
				{
					break;
				}
			}
			ans += 1LL * (cnt* min(1LL * A[i].H, top_h) - tot);
			st.push(A[i]);
		}
	}
	printf("%lld\n", ans);
}
int main()
{
	int t;
	scanf("%d", &t);
	while (t--)
	{
		int n;
		scanf("%d", &n);
		for (int i = 0; i < n; i++)
		{
			scanf("%d", &A[i].H);
			A[i].id = i;
		}
		solve(n);
	}
	return 0;
}


Problem F: 我是好人4

思路:一看题意就知道是容斥,可是发现n<=50,一般容斥肯定TLE无误。所以考虑剪枝:1,当当前递归时的最小公倍数已经大于1e9就剪枝。

能够发现递归层数不会超过10层[前10个质数相乘已经大于1e9了]。 2,对输入数据进行优化处理。当n个数中出现有倍数关系的则能够删掉。

出现1时就输出0。

加上这些优化就能过了。

 尽管能过可是还是能找到令该方法TLE的例子。

比方输入50个数,这50个数都是互不相等的质数。那么也会TLE,可是题目有提示:数据是随机生成的,尔等尽可任意乱搞。 所以除非脸黑。一般不会遇到全是互不相等的质数的情况。

#define _CRT_SECURE_NO_DEPRECATE
#include<iostream>
#include<stdio.h>
#include<algorithm>
#include<string>
#include<cstring>
using namespace std;
typedef long long int LL;
const int MAXM = 1e9;
LL p, ans;
int n, cnt, num[55], temp[55], vis[55];
LL gcd(LL x, LL y)
{
	return y ? gcd(y, x % y) : x;
}
void DFS(LL i, LL w, LL k)
{//i:当前的位置。w:之前集合的最小公倍数,k:奇偶
	if (w > MAXM) //剪枝。

{ return; } for (; i < cnt; i++) { p = num[i] / gcd(num[i], w) * w; ans += k*(MAXM / p); DFS(i + 1, p, -k); } } int main() { int t; scanf("%d", &t); while (t--) { scanf("%d", &n); cnt = 0; bool one = false; memset(vis, 0, sizeof(vis)); for (int i = 0; i < n; i++) { scanf("%d", &temp[i]); if (temp[i] == 0) { vis[temp[i]] = 1; } if (temp[i] == 1)//出现1 { one = true; } } if (one) { printf("0\n"); continue; } sort(temp, temp + n); for (int i = 0; i < n; i++) //删除里面的倍数 { for (int j = i + 1; j < n; j++) { if (vis[i] == 0 && vis[j] == 0 && temp[j] % temp[i] == 0) { vis[j] = 1; } } if (vis[i] == 0) { num[cnt++] = temp[i]; } } ans = 0; DFS(0, 1, 1); printf("%lld\n", MAXM - ans); } }


Problem G: 我是水题

思路:就是水题。2333
#include<iostream>
#include<cstring>
#include<string>
#include<algorithm>
#include<stdio.h>
#include<set>
using namespace std;
typedef long long int LL;
const int MAXN = 10000;
char str[MAXN];
int main()
{
	int t;
	scanf("%d", &t);
	getchar();
	while (t--)
	{
		gets(str);
		set<char>word;
		for (int i = 0; i<strlen(str); i++)
		{
			if (str[i] >= 'a'&&str[i] <= 'z'&&!word.count(str[i]))
			{
				word.insert(str[i]);
			}
		}
		printf("%d\n", word.size());
	}
	return 0;
}


posted @ 2018-01-18 18:41  llguanli  阅读(139)  评论(0编辑  收藏  举报