广工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;
}
时间: 2024-07-30 02:59:10

广工2016校赛决赛的相关文章

ACM学习历程—广东工业大学2016校赛决赛-网络赛F 我是好人4(数论)

题目链接:http://gdutcode.sinaapp.com/problem.php?cid=1031&pid=5 这个题目一看就是一道数论题,应该考虑使用容斥原理,这里对lcm进行容斥. 不过直接上去是T,考虑到序列中同时存在i和ki的话,其实只需要考虑i,所以先对序列中为倍数的对进行处理. 这里的容斥用了hqw的写法. 代码: #include <iostream> #include <cstdio> #include <cstdlib> #includ

ACM学习历程—广东工业大学2016校赛决赛-网络赛E 积木积水(最值问题 || 动态规划)

题目链接:http://gdutcode.sinaapp.com/problem.php?cid=1031&pid=4 这个题目自然会考虑到去讨论最长或者最短的板子. 笔上大概模拟一下的话,就会知道,假设最长的板子是r,0和n+1位置上都是高度为0的板子,那么对于[0, r-1]中的最长板子rr,rr到r这一短应该都是被深度为a[rr]的水覆盖.同样的[0, rr-1]中的最长板子rrr,rrr到rr这一段应该是被a[rrr]覆盖,以此类推可以搞定r的前面一段,同理搞定后一段. 关于最值这一块,

ACM学习历程—广东工业大学2016校赛决赛-网络赛D 二叉树的中序遍历(数据结构)

题目链接:http://gdutcode.sinaapp.com/problem.php?cid=1031&pid=3 这算是一个胡搞类型的题目.当然肯定是有其数据结构支撑的. 唯一的限制就是不能出现连续的两个’#’. 因为如果我从左到右构造这棵树,那么假设我构造到第i个, 如果i+1是数字,那么把前i个构成的子树作为i+1的左儿子即可. 如果i+1是’#’,那么把’#’当成i的右儿子即可. 所以只要没有两个连续的’#’,自然能通过上面的方法构造. 但是如果出现两个连续的’#’,自然前一个’#’

ACM学习历程—广东工业大学2016校赛决赛-网络赛C wintermelon的魔界寻路之旅(最短路 &amp;&amp; 递推)

题目链接:http://gdutcode.sinaapp.com/problem.php?cid=1031&pid=2 题目由于要找对称的路径,那么狠明显可以把右下角的每一块加到左上角对应的每一块上.然后就变成从左上角走到对角线的最短路径的个数. 先跑一遍最短路径得到p(i, j)从起点到(i, j)的最短路径. 然后就是找最短路径的个数.显然cnt(i, j)是它周围点能通过最短路径到它的cnt的和.这一处可以使用记忆化搜索来完成. 代码: #include <iostream> #

广工2017校赛-F-- tmk找三角

http://gdutcode.sinaapp.com/problem.php?cid=1056&pid=5 Description 有一棵树,树上有只tmk.他在这棵树上生活了很久,对他的构造了如指掌.所以他在树上从来都是走最短路,不会绕路.他还还特别喜欢三角形,所以当他在树上爬来爬去的时候总会在想,如果把刚才爬过的那几根树枝/树干锯下来,能不能从中选三根出来拼成一个三角形呢? Input 第一行输入一个T,表示有多少组样例. 对于每组数据:第一行包含一个整数 N,表示树上节点的个数(从 1

广州工业大学2016校赛 F 我是好人4 dfs+容斥

Problem F: 我是好人4 Description 众所周知,我是好人!所以不会出太难的题,题意很简单 给你n个数,问你1000000000(含1e9)以内有多少个正整数不是这n个数任意一个的倍数 最后友情提供解题代码(我真是太好人了) void solve(int p[], int n) { int ans = 0; for (int i = 1; i <= 1e9; i++) { int fl = 0; for (int j = 0; j < n; j++) { if (i % p[

2016BUAA校赛决赛

A. 题意:有n个点,n-1条边,1-2-3-4-5-...-n,每条边都有权值,代表走这条边的时间,时刻0一个人在点1,问从时刻1~m,有哪些时刻这个人可能走到n点 分析:将每条边当作物品,可以选1 3 5 7...次,完全背包,f[i][j]表示前i条边,时刻j能否到,注意如果f[i][j]为1的时候,那么break循环节省时间不需再枚举后面的k C. 题意:圆柱体侧面展开,问两个点的最短距离 分析:勾股定理算一算,注意pi=acos(-1.0) I.  题意:无穷大的平面上,有间距为X的水

BUPT 2016校赛-Binary Strings 概率DP

题目貌似是不对外开放的. 题意: 给你一个01串,其中会有未知个数的? 可以为0或1,问这个01串的任意一个子串能整除3的个数的期望. 忽略前导0. 输入 一个 T 包含T组数据(1≤T≤50) 接下来T组中,一个N (1≤N≤100000) N长度的01?串 输出 保留2位小数. 题解 dp[i][j] (j=0,1,2)表示第i位余数为j的个数 出现一个0,就直接给sum+1,出现?sum+0.5 剩下的就是dp[i][0]的个数. #include<cstdio> #include<

2017浙江工业大学-校赛决赛 竹之书

Description 由于某些原因菲莉丝拿到了贤者之石,所以好像变得很厉害了好像变得很厉害的菲莉丝想要炼成幻想乡,其中有一个原料是稗田一族对幻想乡历史的记录.现在菲莉丝拿到了一个被某只魔粘性精神体加密过的的卷轴.密文通过原文和一个正整数key加密形成,而key和密文又有一定关联.现给出密文,求key值 已知密文s和key值关系如下已知密文s是一串正整数s1,s2,s3--sn,A为s中所有元素的和,B为s中所有元素的积,key为B mod A 数据范围si,A在(0,1e17]范围内0<n<