2017.12.23 浙江工大 迎新赛
比赛链接 :https://www.nowcoder.com/acm/contest/63#question
A .
题目描述
小杰是学院中公认的计算机大神,有一天小杰受邀去机房帮忙接网线,他三下五除二地就帮忙完成了。可是善于思考地他仍意有未尽,他觉得这次的线路有许多奇妙的性质,故来请教你。
机房有n台不同的主机(编号为0,1,2,⋯,n−1),共连有n条网线,每台主机恰连着两条网线,通向其他的主机。相互之间有网线相连的主机可以通信,主机可以转发其他主机的信息,换句话说,如果主机A和主机C可以通信,主机B和主机C可以通信,那么主机A和主机B可以通信。现在想在一些主机间添加一些网线,使得任意一个主机故障时,其他的主机之间仍能互相通信(注:在一个主机故障时,所有与该主机直接相连的线路将无法使用)。那么问题来了,在添加的网线最少时,请问有多少种连线的方案,由于连线方案可能非常多,请将方案数对1000,000,007取模。
输入描述:
第一行表示主机数n。
接下来有n行,第i(i=0,1,2,⋯,n−1)行有两个数a,b,表示主机i和主机a,主机i和主机b各有网线相连。
输入有多组数据,n=0时,表示输入结束,请不要有对该组数据任何输出。
输出描述:
每组数据输出一行,表示方案数对1000,000,007取模的结果。(行末不要有多余的空白字符)
示例1
输入
5 2 3 4 4 0 3 0 2 1 1 12 11 11 10 10 9 9 8 8 7 7 6 6 5 5 4 4 3 3 2 2 1 1 0 0 2 1 1 0 0 0
输出
6 3 0
备注:
2≤n≤3×105,
不超过10组数据 n≥2×105,
不超过20组数据 n≥104,
总数据组数不超过200
题目分析 : 其实题目就是让找有多少个联通的图,并且找到每个独立的图中有多少个点,要将所有的图都连通,且保证当只有一个主机坏的时候,其他主机仍保持连通,则可以从每个独立的块中选择两个点出来去进行相连,C(n, 2),每面对两个独立的图,则会在乘以 2 ,因为可以交换点的顺序么,然后再是所有独立连通图的排列顺序,有(n-1)! 种 。
补充两个很坑的点 !!!
并查集的路径压缩部分,以前学的版本他路径压缩是不完全的 !! , 必须得最后在遍历一遍补充一些点进行路径压缩 !!!
第二点就是 long long 的地方, 拼经验的地方, long long ans 要先给大数值 !!! 虽然还不知道为什么 !!
#define ll long long const int eps = 3e5+5; const ll mod = 1000000007; const double pi = acos(-1.0); const int inf = 1<<29; #define Max(a,b) a>b?a:b #define Min(a,b) a>b?b:a int f[eps]; int num[eps]; int n; ll jc[eps]; ll fang[eps]; int fid(int x){ //return x==f[x]?x:fid(f[x]); int r = x; while(r != f[r]){ r = f[r]; } int p = x; while(p != r){ int t = f[p]; f[p] = r; p = t; } return r; } void fun(){ fang[0] = 1; jc[0] = 1; for(ll i = 1; i <= 300000; i++){ jc[i] = jc[i-1]*i; fang[i] = fang[i-1]*2; jc[i] %= mod; fang[i] %= mod; } } int main() { //freopen("in.txt", "r", stdin); //freopen("out.txt", "w", stdout); int a, b; fun(); map<int, int>mp; while(~scanf("%d", &n) && n){ for(ll i = 0; i <= 300000; i++) f[i] = i; for(int i = 0; i < n; i++){ scanf("%d%d", &a, &b); int fi = fid(i); int fa = fid(a); if (fi != fa){ f[fa] = fi; } int fb = fid(b); if (fi != fb) f[fb] = fi; } mp.clear(); for(int i = 0; i < n; i++){ f[i] = fid(i); // 补充路径压缩 !! mp[f[i]]++; } int cnt = mp.size(); map<int, int>::iterator it; ll ans = jc[cnt-1]*fang[cnt-1]%mod; //ll ans = 1; // 这样写就有问题了 !!! 虽然不知道为什么 for(it = mp.begin(); it != mp.end(); it++){ int t = it->second; ans *= 1ll*t*(t-1)/2%mod; ans %= mod; } //ans *= jc[cnt-1]%mod; //ans *= fang[cnt-1]%mod; //ans %= mod; if (cnt == 1) printf("0\n"); else printf("%lld\n", ans%mod); } return 0; }
C .
题目描述
栗酱在玩一个激燃的战争游戏,游戏里有n个据点,据点之间存在一些道路,将据点两两连通。
栗酱已经占据了一些据点,在他/她占领的每个据点中,他/她都至少安排了一个士兵,他/她可以在相邻的己方据点间移动任意士兵任意次。
如果一个据点没有栗酱的士兵,说明这个据点是属于敌方的。
现在在敌人的进攻之前,栗酱希望能够集中兵力防守,所以要把所有士兵调集到直接和敌人相邻的据点。
但是他/她想让你写个程序算一下这样安排之后,所有与敌人结点直接相邻的己方结点的士兵数中的最小值最大是多少。
为了避免一个己方据点成为敌方据点,每个据点至少需要保留一名士兵,注意士兵移动只能由己方据点到达相邻己方据点,不能穿越敌方据点。
有些时候也会出现没有敌方结点,或者没有己方据点,或者没有敌方据点和己方据点连通的简单情况,对于这种情况,你的程序应该能及时识别出来,并给出"−1"(不包括引号)。
输入描述:
第一行一个数据组数T(T≤50)。
每组数据第一行n,给出结点总数(n≤100),下一行给出n个数字,表示每一个结点有几名栗酱的士兵,如果没有士兵,则代表该结点属于敌方。
之后给出一张n行n列的邻接矩阵,′Y′表示第i个结点和第j个结点存在道路将其连通。
输出描述:
对于每组数据每行给出一个数ans,表示为所求的答案,即所有与敌人结点直接相邻的己方结点的士兵数中的最小值最大是多少。
示例1
输入
2 7 4 1 4 0 6 3 6 NNNNYNN NNNNYNN NNNNNNN NNNNNNN YYNNNNY NNNNNNN NNNNYNN 2 0 7 NY YN
输出
-1 7
题目分析 : 注意题目的一个关键字 与有敌方的点相连的点中最小值中的最大值,首先 你得保证最小值,其次是让最小值最大,我就是坑在了这里 !!
如何让与每个敌人相连的是最小得情况呢, 很显然就是要找到连通的边中点的全部值和,再减去总共的点数,就是现在余下的可操作的点数,然后让这个值除以此连通图与敌人相连的点数,再加一,最后一定是要最小值 !!!
代码示例 :
int a[105]; char mp[105][105]; int b[105]; int f[105]; ll sum[105]; ll num[105]; int vis[105]; int find(int x){ return x==f[x]?x:find(f[x]); } int main() { //freopen("in.txt", "r", stdin); //freopen("out.txt", "w", stdout); int t, n; cin >> t; while(t--){ cin >> n; memset(mp, '\0', sizeof(mp)); int k = 0; for(int i = 1; i <= n; i++) { scanf("%d", &a[i]); if (a[i] == 0) b[k++] = i; } for(int i = 1; i <= n; i++) scanf("%s", mp[i]+1); if (k == n || k == 0) {printf("-1\n"); continue;} int flag = 0; for(int i = 0; i < k; i++){ for(int j = 1; j <= n; j++){ if (mp[b[i]][j] == 'Y') { flag = 1; break; } } if (flag) break; } if (!flag) {printf("-1\n"); continue;} for(int i = 1; i <= n; i++) f[i] = i; for(int i = 1; i <= n; i++){ if (a[i] == 0) continue; for(int j = 1; j <= n; j++){ if (a[j] == 0) continue; if (mp[i][j] == 'Y'){ int fi = find(i); int fj = find(j); if (fi != fj) f[fj] = fi; } } } memset(sum, 0, sizeof(sum)); memset(num, 0, sizeof(num)); for(int i = 1; i <= n; i++){ f[i] = find(i); sum[f[i]] += 1ll*a[i]; if (a[i] > 0) num[f[i]]++; } ll ans = __LONG_LONG_MAX__; for(int i = 1; i <= n; i++){ if (sum[i]){ memset(vis, 0, sizeof(vis)); ll cnt = 0; for(int j = 0; j < k; j++){ for(int l = 1; l <= n; l++){ if (mp[b[j]][l] == 'Y' && f[l] == i && !vis[l]){ vis[l] = 1; cnt++; } } //printf("cnt = %lld\n", cnt); } if (cnt != 0) ans = min(ans, (sum[i]-num[i])/cnt+1); } } if (ans == __LONG_LONG_MAX__) printf("-1\n"); else printf("%lld\n", ans); } return 0; } /* 10 5 0 2 3 4 5 NNYYY NNYYN YYNNY YYNNY YNYYN 9 0 2 3 4 5 0 7 8 9 NYYYYNNNN YNYYNNNNN YYNNYNNNN YYNNYNNNN YNYYNYNNN NNNNYNYYN NNNNNYNNY NNNNYYNNN NNNNNNYNN 4 0 0 0 4 NYNN YNYN NYNN NNNN 4 1 1 1 0 NYNN YNYN NYNN NYNN */
E .题目描述
栗酱有一个长度为n的数列A,一个长度为m的数列B,现在询问A中有多少个长度为m的连续子序列A',
满足(a'1+b1)%k = (a'2+b2)%k = …… = (a'm + bm)%k。
满足(a'1+b1)%k = (a'2+b2)%k = …… = (a'm + bm)%k。
输入描述:
第一行一个数T,表示有T组数据。
对于每组数据,
第一行三个整数,n, m, k。
第一行输入n个数, a1,a2,…,an, 表示A数列中的数,
第二行输入m个数, b1,b2,…,bm, 表示B数列中的数。
输出描述:
每一组数据输出一行,满足条件的连续子序列数量。
示例1
输入
2 3 2 5 7 8 7 8 7 3 2 5 7 8 9 8 7
输出
1 2
备注:
T≤15,
2≤m≤n≤2×105,
1≤ai,bi,k≤109
题目分析 : 要在主串中寻找一个模式串的长度,然后让每一位分别加到模式串中,让他们相加后的和对 k 取余,如果余数相等,则符合题意,结果加一,这里有个小特点, ( a1 + b1) == ( a2 + b2) ( mod k ) , 那么一定就有 ( a1 - a2 ) == ( b2 - b1 ) ( mod k ) , 那么这样一来,不就是对题目给的数据做一个差分嘛,但是 这里有个点要注意一下,就是你差分以后可能会有负数,要加上 k ,到这里准备工作就到这里了。
下面匹配过程和 kmp 就很像了,直接一个KMP就OK了 。
代码示例 :
const int eps = 2e5+5; const double pi = acos(-1.0); const int inf = 1<<29; #define Max(a,b) a>b?a:b #define Min(a,b) a>b?b:a #define ll long long int n, m, k; int a[eps], b[eps]; int nex[eps]; int ans; void getnext(){ int i = 0, j = -1; nex[0] = -1; while(i != m){ if (j == -1 || b[i] == b[j]) nex[++i] = ++j; else j = nex[j]; } } void kmp(){ int i = 0, j = 0; ans = 0; while(i != n-1){ if (j == -1 || a[i] == b[j]){ i++; j++; } else j = nex[j]; if (j == m-1) {ans++; j = nex[j]; } } } int main() { //freopen("in.txt", "r", stdin); //freopen("out.txt", "w", stdout); int t; int x, y; cin >> t; while(t--){ scanf("%d%d%d", &n, &m, &k); scanf("%d", &x); x %= k; for(int i = 0; i < n-1; i++) { scanf("%d", &y); y %= k; a[i] = x - y; x = y; if (a[i] < 0) a[i] += k; } scanf("%d", &x); x %= k; for(int i = 0; i < m-1; i++){ scanf("%d", &y); y %= k; b[i] = y - x; x = y; if (b[i] < 0) b[i] += k; } getnext(); kmp(); printf("%d\n", ans); } return 0; } /* 2 2 9 7 1 8 5 */
F .题目描述
有不等式y⋅x3≤ n,已知y为正整数,x为大于1的正整数,问当x和y的解数量刚好为m的时候n的最小值,如果不存在输出 -1。
输入描述:
多组数据读入。
每组数据一个数字m,如题所示。
输出描述:
每组数据输出一行,输出答案。
示例1
输入
1
输出
8
说明
当方案恰好只有一种的时候,n的最小值为8,此时y=1,x=2。
备注:
1 ≤ m ≤ 1016
题目分析 : 给你一个不等式,每组 x, y 都对应着一组解,让你判断有解的个数是否恰好等于所输入的 n 值,这里有点小问题,最初写代码的时候发现题意都没有弄明白,突然醒悟,并不是所有的解的个数都是符合不等式的,就是比如你 y , x 确定以后,你解的个数也就是确定的,它有可能不符合你所要求的,之前打了个表,,觉得对所有输入的 m 值都是有解的 。
代码示例 :
#define ll long long const int eps = 1e6+5; const ll rr = 1e17; const double pi = acos(-1.0); const int inf = 1<<29; #define Max(a,b) a>b?a:b #define Min(a,b) a>b?b:a ll check(ll x){ ll sum = 0; ll i = 2; while( 1 ){ if (i*i*i <= x){ sum += x/(i*i*i); i++; } else break; } return sum; } int main() { //freopen("in.txt", "r", stdin); //freopen("out.txt", "w", stdout); ll m; while(~scanf("%lld", &m)){ ll l = 1, r = rr; ll ans = 0; while(l <= r){ ll mid = l + (r - l)/2; ll pp = check(mid); if ( pp == m){ ans = mid; } if (pp >= m) r = mid - 1; else l = mid +1; } if (ans == 0) printf("-1\n"); else printf("%lld\n", ans); } return 0; }
G . 题目描述
给定两个长度为n的整数列A和B,每次你可以从A数列的左端或右端取走一个数。假设第i次取走的数为ax,则第i次取走的数的价值vi=bi⋅ax,现在希望你求出∑vi的最大值。
输入描述:
第一行一个数T,表示有T组数据。
对于每组数据,第一行一个整数n,
接下来两行分别给出A数列与B数列。
输出描述:
每一组数据输出一行,最大的∑vi
。
示例1
输入
2 2 1 1000 2 1 5 1 3 5 2 4 1 2 3 4 5
输出
2001 52
说明
对于第二个样例,
第一次从左边取走a1,v1=a1⋅b1=1,
第二次从左边取走a2,v2=a2⋅b2=6,
第三次从右边取走a5,v3=a5⋅b3=12,
第四次从右边取走a4,v4=a4⋅b4=8,
第五次取走剩下的a3,v5=a3⋅b5=25。
总价值∑vi=1+6+12+8+25=52
备注:
T≤10
1≤n≤103
1≤ai,bi≤103
题目分析 : 对于每次操作,你只有两种选择,从左边选择数,或者从右边选择数,这就是个 dp, 我是没想出来,,,很菜 ... 看的题解
dp [ i ][ j ] 表示从左边选取 i 个数从右边选取 j 个数的最大值, 当 i + j == n 时,此时的这个 dp 值就是符合题意的值 ,记录 下来
代码示例 :
const int eps = 1e6+5; const double pi = acos(-1.0); const int inf = 1<<29; #define Max(a,b) a>b?a:b #define Min(a,b) a>b?b:a #define ll long long ll a[1005], b[1005]; ll dp[1005][1005]; int main() { //freopen("in.txt", "r", stdin); //freopen("out.txt", "w", stdout); int t, n; cin >> t; while(t--){ cin >>n; for(int i = 1; i <= n; i++) scanf("%lld", &a[i]); for(int i = 1; i <= n; i++) scanf("%lld", &b[i]); ll ans = 0; memset(dp, 0, sizeof(dp)); for(int i = 0; i <= n; i++){ for(int j = 0; j <= n; j++){ if (i + j > n) continue; if (i == 0) dp[i][j] = max(1ll*0, dp[i][j-1]+a[n+1-j]*b[i+j]); else if (j == 0) dp[i][j] = max(dp[i-1][j]+a[i]*b[i+j], 1ll*0); else dp[i][j] = max(dp[i-1][j]+a[i]*b[i+j], dp[i][j-1]+a[n+1-j]*b[i+j]); if (i + j == n) ans = max(ans, dp[i][j]); } } printf("%lld\n", ans); } return 0; }
K .题目描述
自从学姐拒绝了qwb之后,qwb开始了疯狂的骚扰。qwb来到了一个公共电话亭,他摸摸口袋只有n元钱。
已知该公用电话的规则是,前3分钟一共收费x元(不到3分钟也要收x元),超过3分钟每分钟收费y元(不到1分钟也要收y元)。(先扣钱再打电话。)
那么问题来了,qwb最多骚扰学姐几分钟?(假设学姐不会挂qwb电话)
已知该公用电话的规则是,前3分钟一共收费x元(不到3分钟也要收x元),超过3分钟每分钟收费y元(不到1分钟也要收y元)。(先扣钱再打电话。)
那么问题来了,qwb最多骚扰学姐几分钟?(假设学姐不会挂qwb电话)
输入描述:
第一行输入一个整数T,表示数据组数,
接下来T行,每行三个整数n,x,y 。
输出描述:
每行输出一个整数,表示qwb最多骚扰学姐的分钟数。
示例1
输入
2 10 5 1 5 4 1
输出
8 4
备注:
1≤T≤10000,
1≤n,x,y≤10000
题目分析 : 坑题 , 理所应当的认为一但拨打了电话就不能挂断 。
代码示例 :
const int eps = 1e6+5; const double pi = acos(-1.0); const int inf = 1<<29; #define Max(a,b) a>b?a:b #define Min(a,b) a>b?b:a #define ll long long int main() { //freopen("in.txt", "r", stdin); //freopen("out.txt", "w", stdout); int t; ll n, x, y; cin >> t; while(t--){ scanf("%lld%lld%lld", &n, &x, &y); if (n < x) {printf("0\n"); continue;} if (x >= 3*y) { ll sum = 3; sum += (n-x)/y; printf("%lld\n", sum); } else { ll sum = n/x*3; sum += (n%x)/y; printf("%lld\n", sum); } } return 0; }
东北日出西边雨 道是无情却有情