“Wishare杯”南邮第八届大学生程序设计竞赛之现场决赛 题解报告

时间:2022-10-01 16:25:52

A.爆炸吧,现充 (红)

时间限制:1000ms           内存限制:65536K

题目描述:

a协有部分脱团分子,日复一日,年复一年地进行秀恩爱虐狗行为,对其他成员持续造成着精神伤害。Kojimai君表示在异端分子长期惨无人道的精神攻击下,早早的患上了少年痴呆症。为了应对这一症状,不得不经常把日常琐事记录下来,时间一长整本笔记本都记完了,他现在好奇自己一共记下了多少字,已知笔记本共n页,每页m行,因为心理因素,他排斥偶数页号,所以只在奇数页号的页面写字,又因为痴呆他在奇数页的第i行都只写i个字(1 <= i <= m)。

 

输入:

第一行为一个整数T代表数据组数 T < 10,接下来T行,每行两个数字n和m,均为小于1000的正整数。

 

输出:

Kojimai君记下的字数。

 

样例输入:

2

3 3

2 4

 

样例输出:

12

10


题目分析:这个不会,就比较尴尬了

#include <cstdio>

int main()
{
int T;
scanf("%d", &T);
while(T --)
{
int n, m, ans = 0;
scanf("%d %d", &n, &m);
for(int i = 1; i <= n; i++)
if(i % 2)
ans += m * (m + 1) / 2;
printf("%d\n", ans);
}
}



B.汉诺塔最多步数 (绿)

时间限制:1000ms           内存限制:65536K

题目描述:

汉诺塔是一个很经典的游戏,它有A,B,C三个柱子,目标是把A上面的n个盘子一起移动上B或C任意一个柱子上,每次移动只能移动一个盘子从一个柱子到另一个柱子上,并且移动的时候不能把大的盘子放到小的盘子上。我们的问题是:如何用最多步数完成这个游戏,当然为了避免无限循环,我们只允许每个局面最多出现一次。

 

输入:

输入第一行为一个整数T(T<=1000)代表数据组数,每组数据只有一个数字n(1<=n<=38),表示一共有n个盘子。

 

输出:

输出为一个数字,表示最多步数(输出保证不会超过int64的最大值)。

 

样例输入:

2

1

2

 

样例输出:

2

8


题目分析:假设我们要全部移动到C上,把前n-1个盘子从A柱移到C柱,把第n个判移到B柱,又把那n-1个从C柱移回A柱,再把第n个移到C柱,最后把n-1个移到C柱,为什么这样不会出现重复的状态呢?因为我们假设前n-1个盘子的移动过程中没有重复状态,而三次对n-1的调用时整个状态由于第n个盘子的位置不同而不同。移动的次数是3^n-1。因为设n个盘子的移动次数为f(n),根据以上描述可以得到f(n) = 3 * f(n - 1) + 2,f(0) = 0,则可推出f(n) = 3^n - 1,为什么这样的操作步数是最多?显然总的状态数只有3^n个。

#include <cstdio>
#define ll long long

int main()
{
int T, n;
scanf("%d", &T);
while(T --)
{
ll ans = 1ll;
scanf("%d", &n);
for(int i = 1; i <= n; i++)
ans *= 3ll;
printf("%I64d\n", ans - 1ll);
}
}



C. 绝望的KSS(深蓝)

时间限制:1000ms           内存限制:65536K

题目描述:

         你经历过绝望吗?

         KSS经历过,当年KSS一人困在圆形孤岛上。因为KSS是个强迫症,所以他一直呆在这个圆形孤岛的圆心O处。

         今日,KSS终于等来了远方的客(救)人(星),这位客人现在位于E点,KSS使用他的超能力获得了客人与他的距离(比如使用QQ),但是这位客人江湖人称PDF,有个奇怪的癖好,他要KSS回答一个问题。他射出一道激光,在进入这个圆岛和离开这个圆岛的时候分别留下了一个印记D和B。PDF要KSS求出PDF距离D的距离和距离B的距离,否则就不会去营救KSS。

“Wishare杯”南邮第八届大学生程序设计竞赛之现场决赛 题解报告

         KSS祈祷上苍,请求援助,上苍说:我将EO相连,交圆于C,A两点,我能告诉你AB的距离d,剩下的,你自求多福吧。

         KSS绝望了,你能帮帮他吗?当然KSS也不是吃白饭的,被困多日的他早已使用超能力获得了圆岛的半径r。 (PDF表示让他救KSS,其实他是拒绝的)

 

输入:

         第一行T(T<=100),是测试数据的数量。以下T行,每行三个数,分别为AB的距离d,圆岛半径r,AE距离。所有数据在double范围内,d<sqrt(2)*r,AE>2*r。

 

输出:

         每个样例输出一行,输出ED和EB的距离,用空格隔开,答案保留两位小数。

 

样例输入:

2

1.8365856625621313  94.70069211062373  298.415845341548

7.187623220586373  10.91566993868142  44.94354413616857

 

样例输出:

109.02 298.40

24.09 43.11


题目分析:首先有一个结论,圆的内切四边形对角互补,那么就可以得到三角形ECD相似于三角形EBA,则DE/AE=CE/BE=CD/AB,然后余弦定理cos角BAE=(AB^2+AO^2-BO^2)/(2ABAO)=(AB^2+AE^2-BE^2)/2ABAE,两式联立,即可解得DE和BE,这题不用相似也可以做,这里就不说了。

#include <cstdio>
#include <cmath>

int main()
{
int T, n;
scanf("%d", &T);
while(T --)
{
double AB, R, AE;
scanf("%lf %lf %lf", &AB, &R, &AE);
double BE = sqrt(AB * AB + AE * AE - AB * AB * AE / R);
double DE = (AE - 2.0 * R) * AE / BE;
printf("%.2f %.2f\n", DE, BE);
}
}


D. 有趣的编码 (紫)

时间限制:4000ms           内存限制:32768 K

题目描述:

有种有趣的编码方式,它不仅被广泛应用于各工程领域,还经常出现在广大互联网公司的笔试面试中,现规定如下几个规则:

1.第一个码为全0。

2.任意一个码只包含0和1两个数字。

3.任意两个相邻的码只有一位数字不同。

4.第一和最后一个码也只有一位数字不同。

5.一个n位码组一共2的n次方个编码且任意两个编码不同

6.在满足前5点的条件下,若当前有多个符合条件的编码则选字典序最小的那个,比如2位的码组是

0 0

0 1

1 1

1 0

而不是

0 0

1 0

1 1

0 1

学过数电的同学都知道是什么了吧?

现在请你找出n位码组的第k个编码。

 

输入:

第一行为一个整数T代表数据的组数,每组数据包含两个数字n和k ()。

 

输出:

对于每组数据输出其n位码组的第k个编码。

 

样例输入:

3

1 1

3 5

4 8

 

样例输出:

0

1 1 0

0 1 0 0


题目分析:这种编码其实叫格雷码,原题是直接打印全部的n位格雷码的,输出数据太多就改成第k个了,可以直接用十位数的2进制和格雷码的关系搞,这个百度格雷码即可,当然也可以一个一个推到第k个,递推基本就是找规律了,因为有对称的性质

#include <cstdio>  
#define judge (i % (1 << j)) < ((1 << j) >> 1)
int main()
{
int n, k, T;
scanf("%d", &T);
while(T--)
{
scanf("%d %d", &n, &k);
int cnt = 0;
for(int i = 0; i < (1 << n); i++)
{
cnt ++;
if(cnt == k)
{
for(int j = n; j >= 1; j--)
{
if((i / (1 << j)) & 1)
printf("%d%c", judge ? 1 : 0, j == 1 ? '\n' : ' ');
else
printf("%d%c", judge ? 0 : 1, j == 1 ? '\n' : ' ');
}
}
}
}
}



E. 强大的JP (黄)

时间限制:1000ms           内存限制:65536K

题目描述:

         JP体内有阴阳之力,阴之力的数量为x,阳之力的数量为y,他们都是在1到k之间的正整数(即1<=x<=k,1<=y<=k),为了控制体内的阴阳平衡,|x^2-xy-y^2|必须等于1,他的实力H=x^2+y^2。如今他面临一场世纪之战,他要爆发出最大的实力,也就是说要使得x^2+y^2最大,请告诉他他要如何控制体内的阴阳之力,即阴之力和阳之力的值需要是多少?

 

输入:

第一行为一个整数T(T<=100),表示有T组数据。每组数据为一个正整数n,这里0<n<1018

 

输出:

每组数据输出一个整数m表示计算结果。

 

样例输入:

2

2

8

样例输出:

2 1

8 5


题目分析:对于能满足那个式子的任意x,y,打表找规律即可。

#include <cstdio>
#define ll long long
int const MAX = 105;
ll const INF = 1e18;
ll y[MAX], t[MAX], x[MAX];
int cnt;

void pre()
{
y[0] = 1;
y[1] = 2;
t[0] = 3;
t[1] = 4;
x[0] = (y[0] + t[0]) >> 1;
x[1] = (y[1] + t[1]) >> 1;
cnt = 2;
while(true)
{
y[cnt] = y[cnt - 1] + y[cnt - 2];
if(y[cnt] > INF)
break;
t[cnt] = t[cnt - 1] + t[cnt - 2];
x[cnt] = (y[cnt] + t[cnt]) >> 1;
cnt ++;
}
}

int main()
{
pre();
int T;
scanf("%d",&T);
while(T--)
{
ll k, ansx, ansy;
scanf("%I64d", &k);
for(int i = 0; i < cnt; i++)
{
if(y[i] <= k && x[i] <= k)
{
ansx = x[i];
ansy = y[i];
}
else
break;
}
printf("%I64d %I64d\n", ansx, ansy);
}
}



F. 粗心的工作人员(粉)

时间限制:10000ms         内存限制:32768K

题目描述:

大牛BJ去某巨头互联网公司参加面试,该公司HR不喜欢奇数,因此事先给所有面试者的编号都是偶数,0开始从小到大按顺序编号(0,2,4,6,8,…),当然一个编号只能给一个面试者,面试者来到公司首先要去签到,粗心的工作人员在记录签到信息时竟忘了打空格,于是得到了类似268104012这样的序列,真正的签到顺序是2,6,8,10,4,0,12。现在已知所有拿到编号将要参加面试的人员都已签到完毕,没人缺席!当然工作人员在记录时还可能把编号输错,现在工作人员想知道自己编号有没有输错。

 

输入:

第一行为一个整数T (T <= 20)代表数据的组数,每组数据只有一行,即一个数字序列S,S的长度不超过700。(注:S大于300时T不大于10)

 

输出:

对于每组数据,若工作人员输入有错则输出一行No,否则输出Yes。

 

样例输入:

4

122861340

2014228182441001268162622

0

2

 

样例输出:

No

Yes

Yes

No

 

样例解释:

第一个显然是无解的。

第二个可以将其正确分解。

第三个只有一个0,所以是正确的。

第四个因为我们强调编号是从0开始发的,又因为所有参加面试人员都到齐,所以有2必定有0,但是只有一个2显然是错误的。


题目分析:这题我出的,坑爹了,题目按道理根据搜索的方向不同是有多组解的(这里需要感谢wxy同学不断的尝试才让我意识到这点),所以后来改了,只需要判断Yes,No即可,不过这并不影响大家过不了这题,因为这题需要很强的剪枝才能过,其实我已经把数据放小且把时限放大了,之前数据更多更大。下面细说这题的每一个剪枝

1.首先根据字符串的长度,我们可以求出当前长度下如果有解,最大的那个数是多少,并且在计算过程中也可以去掉一些No的情况,比如一个长度为10的字符串,减去个位数的长度即0,2,4,6,8,还剩下5,那么剩下的都是十位数,显然长度为5是构成不了若干十位数的

2.既然知道了最大的数字是多少,那么我们就可以知道从0到最大的那个数字在有解得情况下,0-9这10个数字应该出现的次数,然后和输入数据得到的次数做比对,如果有出入那显然是No了

3.搜索过程中分别枚举1位,2位,3位,要注意枚举的数字必须小于等于最大的那个,且必须是偶数,且之前没被标记过

4.这是这题最操蛋的剪枝,不过我把这个剪枝的时间放了,就是搜索前我们可以分别算出个位十位百位分别要搜几个数字,搜索时我们可以加三个变量分别代表个位,十位,百位分别已经标记了的数量,然后通过判断若要搜到解还需的字符串长和当前搜索剩下的长度做比较,如果大于剩下的,那后面的搜索肯定是无法搜到解的,就可以return了

除了刺激没别的词可以形容

#include <cstdio>  
#include <cstring>
int const MAX = 705;
char s[MAX];
int num[MAX], tot[10], tot_[10];
int len, n, GW, SW, BW, ma;
bool flag, vis[MAX];

void DFS(int idx, int cnt, int gw, int sw, int bw)
{
if(flag)
return;
if((GW - gw) + (SW - sw) * 2 + (BW - bw) * 3 > len - idx + 1)
return;
if(idx == len && GW == gw && SW == sw && BW == bw)
{
flag = true;
return;
}
if((gw < GW) && idx < len)
{
num[cnt] = s[idx] - '0';
if(!vis[num[cnt]] && !(num[cnt] & 1) && num[cnt] <= ma)
{
vis[num[cnt]] = true;
DFS(idx + 1, cnt + 1, gw + 1, sw, bw);
if(flag)
return;
vis[num[cnt]] = false;
}
}
if((sw < SW) && idx < len - 1)
{
num[cnt] = 10 * (s[idx] - '0') + s[idx + 1] - '0';
if(num[cnt] <= 2 * (n - 1) && num[cnt] > 9 && !(num[cnt] & 1))
{
if(!vis[num[cnt]])
{
vis[num[cnt]] = true;
DFS(idx + 2, cnt + 1, gw, sw + 1, bw);
if(flag)
return;
vis[num[cnt]] = false;
}
}
}
if((bw < BW) && idx < len - 2)
{
num[cnt] = 100 * (s[idx] - '0') + 10 * (s[idx + 1] - '0') + (s[idx + 2] - '0');
if(num[cnt] <= 2 * (n - 1) && num[cnt] > 99 && !(num[cnt] & 1))
{
if(!vis[num[cnt]])
{
vis[num[cnt]] = true;
DFS(idx + 3, cnt + 1, gw, sw, bw + 1);
if(flag)
return;
vis[num[cnt]] = false;
}
}
}
}

int main()
{
int T;
scanf("%d", &T);
while(T--)
{
scanf("%s", s);
GW = 0;
BW = 0;
SW = 0;
flag = false;
memset(vis, false, sizeof(vis));
memset(num, 0, sizeof(num));
memset(tot, 0, sizeof(tot));
memset(tot_, 0, sizeof(tot_));
len = strlen(s);
for(int i = 0; i < len; i++)
tot[s[i] - '0'] ++;
if(len <= 5)
{
n = len;
GW = n;
}
else if(len > 5 && len <= 95)
{
if((len - 5) % 2)
{
printf("No\n");
continue;
}
else
{
n = 5 + (len - 5) / 2;
GW = 5;
SW = n - 5;
}
}
else
{
if((len - 95) % 3)
{
printf("No\n");
continue;
}
else
{
n = 50 + (len - 95) / 3;
GW = 5;
SW = 45;
BW = n - 50;
bool f = true;
for(int i = 0; i < 2 * n; i += 2)
{
char tmp[10];
sprintf(tmp, "%d", i);
for(int j = 0; j < (int) strlen(tmp); j++)
tot_[tmp[j] - '0'] ++;
}
for(int i = 0; i < 10; i++)
{
if(tot_[i] != tot[i])
{
f = false;
break;
}
}
if(!f)
{
printf("No\n");
continue;
}
}
}
ma = 2 * (n - 1);
DFS(0, 0, 0, 0, 0);
printf("%s\n", flag ? "Yes" : "No");
}
}



G. 步行者(橘)

时间限制:1000ms           内存限制:65536K

题目描述:

         PDF是爱好旅游的怪咖,现在他的地图上有n个城市,我们可以编号为0到n-1,城市之间有k条路,这k条路中,不存在任意两条路的两个端点相同。也就是说,A,B两个城市之间,直接相连的最多有一条路。PDF想要知道,从i走过L条路到达j的走法有多少?PDF对旅游极为热爱,因此他不介意经过一个城市多次。PDF会询问你q次。

 

输入:

         第一行T,是测试数据的数量T<=10。每组数据第一行为两个整数n,k。(0<n<=35,0<k<=n*(n-1)/2),以下k行,每行两个整数a,b,表示编号为a的点和编号为b的点之间有一条边相连。接下来有一行为两个整数q,L (0<q<=10000,0<L<=1e7),接下来有q行,每行两个整数为i,j。

 

输出:

对于每次询问,输出一行,每行一个整数,为从i到j长度为L的走法的数量对1112取模的值。

 

样例输入:

1

9 17

1 2

2 4

3 4

0 5

0 2

5 7

2 8

1 5

5 8

1 3

2 6

0 6

2 7

3 6

4 6

1 4

6 7

1 1808203

1 2

 

样例输出:

23


题目分析:这个题。。。学过离散的同学都知道有一种矩阵叫邻接矩阵吧?不知道的百度一下,然后就是裸的矩阵快速幂了

#include <cstdio>  
#include <cstring>
#include <algorithm>
using namespace std;
int const MOD = 1112;
int n, k;
struct matrix
{
int m[40][40];
}a;

matrix multiply(matrix x, matrix y)
{
matrix ans;
memset(ans.m, 0, sizeof(ans.m));
for(int i = 0; i < n; i++)
for(int j = 0; j < n; j++)
if(x.m[i][j])
for(int k = 0; k < n; k++)
ans.m[i][k] = ((ans.m[i][k] % MOD) + ((x.m[i][j] % MOD) * (y.m[j][k] % MOD))) % MOD;
return ans;
}

matrix quickmod(matrix a, int p)
{
matrix ans;
memset(ans.m, 0, sizeof(ans.m));
for(int i = 0; i < n; i++)
ans.m[i][i] = 1;
while(p)
{
if(p & 1)
ans = multiply(ans, a);
p >>= 1;
a = multiply(a, a);
}
return ans;
}

int main()
{
int T;
scanf("%d", &T);
while(T --)
{
matrix a;
memset(a.m, 0, sizeof(a.m));
scanf("%d %d", &n, &k);
while(k --)
{
int x, y;
scanf("%d %d", &x, &y);
a.m[x][y] = 1;
a.m[y][x] = 1;
}
int q, L;
scanf("%d %d", &q, &L);
matrix ans = quickmod(a, L);
while(q --)
{
int i, j;
scanf("%d %d", &i, &j);
printf("%d\n", ans.m[i][j] % MOD);
}
}
}



H. 自动麻将机(白)

时间限制:1000ms           内存限制:65536K

题目描述:

自动机大神恺叔叔自认为掌握所有的自动机,然而雀圣黄大仙不服,表示出一种自动麻将机,赢光恺叔叔所有家当以后请A协所有人吃水林间,被逼疯的恺叔叔于是找到你来帮忙设计这样的自动机,麻将是一种棋牌游戏,具体自动机的功能如下:现给你一副当前的牌,求问当上家出什么牌的时候可以进行吃、碰、杠、胡四种操作。

其中,

一条至九条表示为A1、A2...A9

一万至九万表示为B1、B2...B9

一柄至九柄表示为C1、C2...C9

东风、南风、西风、北风、红中、发财、白板分别表示为D1、D2...D7 

麻将具体规则如下:

吃:上家打出牌,与手中的牌正好组成一副顺子(即相同牌类型,相连三章牌,例如:一条,二条,对方打出三条即可),他就可以吃

碰:上家打出牌,与手中的牌正好组成一副刻子(即相同的牌,例如:手中有两张北风,对方再次打出一张北风),他就可以碰

杠:其他人打出一张牌,自己手中有三张相同的牌(例如:手中有三张北风,对方再次打出一张北风),即可杠牌 

胡:将自己手中牌和对方打出牌和一起可满足特定情况,具体胡有三种情况:

普通方法:一副将牌(两张相同的牌)+四组刻子或者顺子(刻子为三张相同的牌,顺子为三张相同类型,连续的牌),例如:

 “Wishare杯”南邮第八届大学生程序设计竞赛之现场决赛 题解报告 

对对胡:7副对牌(对牌即为两张相同的牌),例如:

 “Wishare杯”南邮第八届大学生程序设计竞赛之现场决赛 题解报告

十三幺:一柄,九柄,一条,九条,一万,九万,东风,南风,西风,北风,红中,发财,白板各一张,其中一种有两张,例如:

 “Wishare杯”南邮第八届大学生程序设计竞赛之现场决赛 题解报告

 

输入:

本题为多组样例输入,首先输入一个整数t(t<= 1010),表示样例个数,对于每组样例:

第一行输入13个字符串,表示手中的13张牌。

 

输出:

对于每组样例,输出四行,第一行输出所有可以吃的牌,第二行输出所有可以碰的牌,第三行输出所有可以杠的牌,第四行输出所有可以胡的牌,牌与牌之间用空格隔开,样例与样例之间输出一个空行,如果当前操作没有牌可以完成,则输出“NONE”,同一操作的牌按照字典序排列。

 

样例输入:

2

A1 A2 A3 A4 A5A6 A7 A8 A9 A9 B1 B2 B3

A1 A1 B8 B8 C8C8 D1 D1 D2 D2 D5 D5 D6

 

样例输出:

A1 A2 A3 A4 A5A6 A7 A8 A9 B1 B2 B3 B4

A9

NONE

A3 A6 A9

 

NONE

A1 B8 C8 D1 D2D5

NONE

D6


题目分析:听说这叫大模拟。。。于是直接贴pdf的标程了。

#include <cstdio>
#include <iostream>
#include <cstring>
#include <string>
#include <algorithm>
#include <map>
#include <vector>

using namespace std;

char str[14][3];
bool flag[14];
int mapp[4][10];

int cmp(const void *a, const void *b)
{
return strcmp((char*)a, (char*)b);
}

bool dfs(int deep)
{
if(deep == 0)
return true;
int i = 0;
int p1 = -1, p2 = -1, p3 = -1;
while(i < 14 && !flag[i])
i++;
p1 = i++;
while(i < 14 && (!flag[i] || strcmp(str[p1], str[i]) != 0))
i++;
p2 = i++;
while(i < 14 && (!flag[i] || strcmp(str[p1], str[i]) != 0))
i++;
p3 = i;
if(p3 < 14)
{
flag[p1] = false;
flag[p2] = false;
flag[p3] = false;
if(dfs(deep - 3))
return true;
flag[p1] = true;
flag[p2] = true;
flag[p3] = true;
}
if(str[p1][0] != 'D' && str[p1][1] >= '1' && str[p1][1] <= '7')
{
char ch[3];
strcpy(ch, str[p1]);
ch[1]++;
i = p1 + 1;
while(i < 14 && (!flag[i] || strcmp(str[i], ch) != 0))
i++;
p2 = i++;
ch[1]++;
while(i < 14 && (!flag[i] || strcmp(str[i], ch) != 0))
i++;
p3 = i;
if(p3 < 14)
{
flag[p1] = false;
flag[p2] = false;
flag[p3] = false;
if(dfs(deep - 3))
return true;
flag[p1] = true;
flag[p2] = true;
flag[p3] = true;
}
}
return false;
}

bool solve()
{
bool pf = true;
for(int i = 0; i < 14; i += 2)
{
if(strcmp(str[i], str[i + 1]) != 0)
{
pf = false;
break;
}
}
if(pf)
return true;
for(int i = 0; i < 13; i++)
{
if(strcmp(str[i], str[i + 1]) == 0)
{
flag[i] = false;
flag[i + 1] = false;
if(dfs(12))
return true;
flag[i] = true;
flag[i + 1] = true;
i++;
}
}
return false;
}

int main()
{
int ti;
scanf("%d", &ti);
while(ti--)
{
memset(mapp, 0, sizeof(mapp));
for(int i = 0 ; i < 13; i++)
{
cin >> str[i];
mapp[str[i][0] - 'A'][str[i][1] - '0']++;
}
vector<string> vec[4];
for(char c = 'A'; c <= 'D'; c++)
{
for(char i = '1'; i <= '9'; i++)
{
if(c == 'D' && i == '8')
break;
char ch[3];
ch[0] = c;
ch[1] = i;
ch[2] = '\0';
if(mapp[ch[0] - 'A'][ch[1] - '0'] >= 4)
continue;
mapp[ch[0] - 'A'][ch[1] - '0']++;
bool f[4];
memset(f, false, sizeof(f));
if(ch[0] != 'D')
{
int tmp = ch[1] - '0';
int item = ch[0] - 'A';
if(tmp - 2 > 0 && mapp[item][tmp - 2] > 0 && mapp[item][tmp - 1] > 0)
f[0] = true;
if(tmp - 1 > 0 && tmp + 1 < 10 && mapp[item][tmp + 1] > 0 && mapp[item][tmp - 1] > 0)
f[0] = true;
if(tmp + 2 < 10 && mapp[item][tmp + 2] > 0 && mapp[item][tmp + 1] > 0)
f[0] = true;
}
if(mapp[ch[0] - 'A'][ch[1] - '0'] >= 3)
f[1] = true;
if(mapp[ch[0] - 'A'][ch[1] - '0'] == 4)
f[2] = true;
char tmp[13][3];
for(int i = 0; i < 13; i++)
strcpy(tmp[i], str[i]);
strcpy(str[13], ch);
qsort(str, 14, sizeof(str[0]), cmp);
memset(flag, true, sizeof(flag));
if(mapp[0][1] > 0 && mapp[0][9] > 0 && mapp[1][1] > 0 && mapp[1][9] > 0 && mapp[2][9] > 0 && mapp[2][9] > 0)
{
bool flag2 = false;
if(mapp[0][1] == 2 || mapp[0][9] == 2 || mapp[1][1] == 2 || mapp[1][9] == 2 || mapp[2][1] == 2 || mapp[2][9] == 2)
flag2 = true;
f[3] = true;
for(int i = 1; i <= 7; i++)
{
if(mapp[3][i] == 0)
{
f[3] = false;
break;
}
if(mapp[3][i] == 2)
flag2 = true;
}
if(flag2 == false)
f[3] = false;
}
if(!f[3])
f[3] = solve();
for(int i = 0; i < 13; i++)
strcpy(str[i], tmp[i]);
for(int i = 0; i < 4; i++)
{
if(f[i])
{
string str = ch;
vec[i].push_back(str);
}
}
mapp[ch[0] - 'A'][ch[1] - '0']--;
}
}
for(int i = 0; i < 4; i++)
{
if(vec[i].size() == 0)
{
cout << "NONE" << endl;
continue;
}
for(int j = 0; j < vec[i].size() - 1; j++)
cout << vec[i][j] << " ";
cout << vec[i][vec[i].size() - 1] << endl;
}
if(ti != 0)
cout << endl;
}
return 0;
}



I. Bojie的电分重修路(淡蓝)

时间限制:4000ms           内存限制:65536K

题目描述:

Bojie因为沉迷于acm竞赛、开源项目以及实习,在大一大二分别两次挂掉了电路分析。终于意识到自己如果再不复习来通过重修就很可能无法毕业的Bojie,在大三再次重修电路分析时候找到了贝院学霸Kss来帮助他复习。然而Kss发现Bojie因为高中物理课上讲的基础知识都忘光了导致连最基础的KCL,KVL都无法理解。于是Kss决定从纯电阻欧姆定律开始讲起。

       给定一个包含N个节点(编号从1到N)的电路图,由N–1条带有不同电阻的导线将N个节点连接起来(保证不存在环路且所有节点均可以两两抵达)。现在Kss会进行Q次提问,每次提问会询问如果将节点u和节点v分别连接到一个电动势为E的理想电源(即无内阻)的正负极上时,通过电源的电流的值是多少。

       尽管Bojie已经下定决心要在大三把电路分析给过掉,但由于他忙于比较各家公司开出的天文数字offer并还在考虑到底该赏脸去哪家,于是他决定把这个问题交给各位校赛参赛选手。

 

输入:

仅一组数据,第一行包含仅一个整数n (2 <= n <=100,000)表示节点总数,第2至第n行每行包含三个整数u, v, R(0 < R <= 100)表示有一根电阻为R的导线连接了节点u和v。第n + 1行仅包含一个整数Q (1<= Q <= 100,000)表示询问总数。第n + 2到第n + Q + 1行每行包含三个整数u, v, E(0 < E <=10^9)表示电源电动势为E的电源的正负极分别接到节点u和v,保证u,v为不同节点。

 

输出:

输出包含Q行,每行包含一个实数分别对应Q次询问的通过电源的电流的值,因为kss是一个严谨的人,所以他希望答案能保留六位小数。C++中格式输出,六位小数的输出表达为 printf("%.6f\n", Current);

 

样例输入:

9

1 2 2

2 3 6

2 4 8

4 5 1

1 6 4

7 6 3

8 6 5

9 6 7

6

1 2 10

5 1 15

5 7 20

3 8 40

1 9 30

3 5 40

 

样例输出:

5.000000

1.363636

1.111111

2.352941

2.727273

2.666667

 

样例解释:

节点1到节点2之间的电阻和为2,电压为10,所以电流为 10 / 2 = 5.000000

节点5到节点1之间的电阻和为11,电压为15,所以电流为 15 / 11 = 1.363636

节点5到节点7之间的电阻和为18,电压为20,所以电流为 20 / 18 = 1.111111

节点3到节点8之间的电阻和为17,电压为40,所以电流为 40 / 17 = 2.352941

节点1到节点9之间的电阻和为11,电压为30,所以电流为30 / 11 = 2.727273

节点3到节点5之间的电阻和为15,电压为40,所以电流为40 / 15 = 2.666667


题目分析:我们的天猫同学出的,然后就是个LCA,在线倍增什么的写一写就过啦

这是他给出的题解:

很容易看出题目给出的电路是一棵树,然后询问是求两点间的边权和,再与一个数相除得到这个询问的答案。
因此显而易见可以利用树链剖分做这道题,将树划分成线段之后,进行两点间的边权求和实际上就转化成了区间求和问题。由于不需要修改,因此剖分之后利用前缀和数组来求区间部分和即可,不需要再挂靠线段树或是树状数组。
同样由于不需要修改,求两点间边权和的问题实际上就变成了求两点分别到最近公共祖先的边权和的和。因此也可以用树上倍增来做,与倍增LCA写法类似不过需要额外加一个sum域来实现求边权和。关于倍增LCA的写法请自行搜索。
两种算法复杂度均为 O(QlogN)
标程为树链剖分+前缀和。
P.S. 本题所有计算都控制在int范围内并且没有给出短路等很trick的情况应该还是很良心的了。

先贴他的树链剖分(我表示不会写)

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
#include <map>
#include <cstdlib>
#include <cmath>
#include <string>

#define N 150000 + 100

#define X first
#define Y second

#define FOR(i,v) for(__typeof(v.begin()) i = v.begin(); i != v.end(); ++i)

#define m_p make_pair
#define p_b push_back

using namespace std;

struct Edge
{
int u,v;
int R;
}E[N];

vector <int> V[N];
int n,pos;
int fa[N];
int deep[N];
int num[N];
int p[N],fp[N]; //fp is the inverse of p
int heavySon[N],topFa[N];

void dfs_ini(int u, int pre, int d)
{
deep[u] = d;
fa[u] = pre;
num[u] = 1;
FOR(i, V[u])
{
if (*i != pre)
{
dfs_ini(*i, u, d + 1);
num[u] += num[*i];
if (heavySon[u] == -1 || num[*i] > num[heavySon[u]])
heavySon[u] = *i;
}
}
}

void dfs_split(int u, int superFa)
{
topFa[u] = superFa;
p[u] = pos++;
fp[p[u]] = u;
if (heavySon[u] == -1)
return;
dfs_split(heavySon[u], superFa);
FOR(i, V[u])
{
if (*i != heavySon[u] && *i != fa[u])
dfs_split(*i, *i);
}
}

int sum[N], Rval[N];

void build_sum()
{
memset(sum,0,sizeof sum);
for (int i = 1; i <= n; ++i)
sum[i] = sum[i - 1] + Rval[i];
}

int part_sum(int ini, int lst)
{
return sum[lst] - sum[ini - 1];
}

int get_ans(int u, int v)
{
int f1 = topFa[u], f2 = topFa[v];
int tot = 0;
while (f1 != f2)
{
if (deep[f1] < deep[f2])
{
swap(f1, f2);
swap(u, v);
}
tot += part_sum(p[f1], p[u]);
u = fa[f1];
f1 = topFa[u];
}
if (u == v)
return tot;
if (deep[u] > deep[v])
swap(u, v);
return tot + part_sum(p[heavySon[u]], p[v]);
}

int main()
{
scanf("%d",&n);
for (int i = 0; i <= n; ++i)
V[i].clear();
for (int i = 1; i < n; ++i)
{
scanf("%d%d%d",&E[i].u, &E[i].v, &E[i].R);
V[E[i].u].p_b(E[i].v);
V[E[i].v].p_b(E[i].u);
}

memset(topFa, -1, sizeof topFa);
pos = 0;
memset(heavySon, -1, sizeof heavySon);
dfs_ini(1,0,0);
dfs_split(1,1);
memset(Rval, 0 ,sizeof Rval);
for (int i = 1; i < n; ++i)
{
if (deep[E[i].u] > deep[E[i].v])
swap(E[i].u, E[i].v);
Rval[p[E[i].v]] = E[i].R;
}
build_sum();

int Q;
scanf("%d", &Q);
int u, v, Voltage, Rtotal;
for (int i = 1;i <= Q; ++i)
{
scanf("%d%d%d",&u, &v, &Voltage);
Rtotal = get_ans(u, v);
double Current = (double) Voltage / (double) Rtotal;
printf("%.6f\n", Current);
}
}

然后是我的在线倍增求LCA

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int const MAX = 100005;
int const POW = 18;
int n, m, cnt;
int p[MAX][20];
int dis[MAX], head[MAX], d[MAX];
bool vis[MAX];
struct Edge
{
int to, nxt, val;
}e[MAX << 1];

inline void Init()
{
cnt = 0;
memset(head, -1, sizeof(head));
}

inline void Add(int u, int v, int w)
{
e[cnt].to = u;
e[cnt].val = w;
e[cnt].nxt = head[v];
head[v] = cnt ++;
}

inline void Get_dis(int u, int fa, int d)
{
dis[u] = d;
for(int i = head[u]; i != -1; i = e[i].nxt)
{
int v = e[i].to;
if(v != fa && !vis[v])
{
vis[v] = true;
Get_dis(v, u, d + e[i].val);
}
}
}

inline void Get_p(int u, int fa)
{
d[u] = d[fa] + 1;
p[u][0] = fa;
for(int i = 1; i < POW; i++)
p[u][i] = p[p[u][i - 1]][i - 1];
for(int i = head[u]; i != -1; i = e[i].nxt)
if(e[i].to != fa)
Get_p(e[i].to, u);
}

inline int Lca(int a, int b)
{
if(d[a] > d[b])
swap(a, b);
if(d[a] < d[b])
{
int del = d[b] - d[a];
for(int i = 0; i < POW; i++)
if(del & (1 << i))
b = p[b][i];
}
if(a != b)
{
for(int i = POW - 1; i >= 0; i--)
{
if(p[a][i] != p[b][i])
{
a = p[a][i];
b = p[b][i];
}
}
a = p[a][0];
b = p[b][0];
}
return a;
}

int main()
{
Init();
int u, v, w;
scanf("%d", &n);
for(int i = 1; i < n; i++)
{
scanf("%d %d %d", &u, &v, &w);
Add(u, v, w);
Add(v, u, w);
}
memset(vis, false, sizeof(vis));
Get_dis(1, -1, 0);
Get_p(1, 0);
scanf("%d", &m);
int x, y, val;
while(m --)
{
scanf("%d %d %d", &x, &y, &val);
printf("%.6f\n", (double) val / (double) (dis[x] + dis[y] - (2 * dis[Lca(x, y)])));
}
}


J. 校庆马拉松接力(黑)

时间限制:8000ms           内存限制:32768K

题目描述:

信达天下,自强不息,一代代南邮人在信息时代的发展征程中披荆斩棘,奋勇前行,今年的4月20日是南京邮电大学办学74周年纪念日,为此学校首次举办连接三牌楼校区和仙林校区的户外跑步赛事,旨在进一步弘扬南邮精神,营造和谐向上的校园文化环境。

由于报名接力的人数过多,为了让更多的学子参与进来,组织方决定将接力的规则修改,一段路不只让一个人跑,这样不但可以增加实际参赛人数,又增添了活动的趣味性,具体规则如下:

比赛以学院为单位,总路线被分成n段,每段路程为米,对每个学院要求第i段路至少需要有个该学院的选手跑过,每个学院自行挑选竞跑选手,在报名的学子中,按个人身体素质分成了m级,第i级选手的平均速度为米每秒,且必须从第段路的起点一直跑到第段路的终点,完成比赛耗费总时间最少的学院获得胜利。(注:这里总时间指的是各学院每个参赛选手跑完自己路段时间的总和)

现在给你xxx学院的报名信息和比赛路段信息,假设该学院每级报名的人数都无限多,请你先判断xxx学院能否完成比赛,在可以完成比赛的情况下你能给出一个分配方案使xxx学院完成比赛所耗费的总时间最短吗?请求出这个最短总时间。

 

输入:

输入第1行为一个整数T代表数组的组数,每组数据第1行为一个整数n表示共n段路,第2行到第n+1行每行2和整数,,分别表示第i段路的长度以及至少需要跑过的人数,第n+2行为一个整数m,代表选手被分成m级,接下来的m行,每行三个整数,,,分别代表第i级选手要跑的路段区间和其平均速度。 

其余数据均不超过100000。

 

输出:

如果不能完成比赛请输出一行No,否则第一行输出Yes,第二行输出所耗费的最短总时间,答案保留两位小数。

 

样例输入:

2

2

1 1

2 2

1

1 1 2

3

1 1

2 2

3 1

3

1 2 2

2 3 2

1 3 3

 

样例输出:

No

Yes

3.50

 

样例解释:

第一组样例第二段路找不到人跑所以无法完成。

第二组样例可以选择1个第一级选手和1个第三级选手,耗时是3.50 (1.50+2.00),若选1个第一级选手和1个第二级选手也可以完成比赛,但耗时为4.00 (1.50+2.50)。


题目分析:这题估计都没什么人看吧。。。这是一道典型的线性规划问题,可以用最小费用最大流解决,这题题解写起来非常的麻烦,我就直接说怎么建图了,至于为什么这样,有兴趣的留言私聊

首先根据前缀和求出每类选手跑自己路段所需要的时间,对于相邻的两段路i和i-1,若第i-1段路需要跑过的人数小于第i段路则从源点向i引容量为d,费用为0的弧,否则从i向汇点引容量为-d,费用为0的弧,在任意两个相邻的路段,从前一段向后一段引容量为无穷大,费用为0的弧,对于每类选手,因为选手数量无限多,所以从起点x到终点y+1引流量为无穷大,费用为跑这段路所要时间的弧,然后跑一个费用流,这样建图是基于一系列线性规划公式变换得到的。还有就是完不成的情况,那么读入的时候直接记录下即可。

对于这题SPFA即可,当然有更快的方法。

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
int const MAX = 1005;
int const INF1 = 0x3fffffff;
double const EPS = 1e-10;
double const INF2 = 1e17;
int n, m, cnt;
int src, sk;
int head[MAX], pre[MAX];
int a[MAX];
double sum[MAX], dis[MAX], ans;
bool vis[MAX], mark[MAX], flag;

struct EDGE
{
int to, nxt, cap;
double cost;
}e[10005 << 2];

void Init()
{
flag = false;
ans = 0;
cnt = 0;
memset(head, -1, sizeof(head));
memset(mark, false, sizeof(mark));
memset(sum, 0, sizeof(sum));
memset(a, 0, sizeof(a));
}

void Add(int u, int v, int cap, double cost)
{
e[cnt].to = v;
e[cnt].cap = cap;
e[cnt].cost = cost;
e[cnt].nxt = head[u];
head[u] = cnt ++;

e[cnt].to = u;
e[cnt].cap = 0;
e[cnt].cost = -cost;
e[cnt].nxt = head[v];
head[v] = cnt ++;
}

void Build_graph()
{
int x, y;
double s, v;
scanf("%d", &n);
src = 0;
sk = n + 2;
for(int i = 1; i <= n; i++)
{
scanf("%lf %d", &s, &a[i]);
sum[i] = sum[i - 1] + s;
}
for(int i = 1; i <= n + 1; i++)
{
int d = a[i] - a[i - 1];
if(d > 0)
Add(src, i, d, 0);
else
Add(i, sk, -d, 0);
if(i > 1)
Add(i, i - 1, INF1, 0);
}
scanf("%d", &m);
for(int i = 0; i < m; i++)
{
scanf("%d %d %lf", &x, &y, &v);
mark[x] = true;
mark[y] = true;
Add(x, y + 1, INF1, (sum[y] - sum[x - 1]) / v);
}
for(int i = 1; !flag && i <= n; i++)
if(a[i] && !mark[i])
flag = true;
}

bool SPFA()
{
queue <int> q;
memset(vis, false, sizeof(vis));
for(int i = 0; i <= sk; i++)
dis[i] = INF2;
dis[src] = 0;
vis[src] = true;
q.push(src);
while(!q.empty())
{
int u = q.front();
q.pop();
vis[u] = false;
for(int i = head[u]; i != -1; i = e[i].nxt)
{
int v = e[i].to;
int cap = e[i].cap;
double cost = e[i].cost;
if(cap > 0 && dis[u] + cost - dis[v] < -EPS)
{
pre[v] = i;
dis[v] = dis[u] + cost;
if(!vis[v])
{
vis[v] = true;
q.push(v);
}
}
}
}
return dis[sk] != INF2;
}

void Augment()
{
while(SPFA())
{
int mi = INF1;
for(int u = sk; u != src; u = e[pre[u] ^ 1].to)
mi = min(mi, e[pre[u]].cap);
for(int u = sk; u != src; u = e[pre[u] ^ 1].to)
{
e[pre[u]].cap -= mi;
e[pre[u] ^ 1].cap += mi;
ans += (double)mi * e[pre[u]].cost;
}
}
}

int main()
{
int T;
scanf("%d", &T);
while(T --)
{
Init();
Build_graph();
if(flag)
printf("No\n");
else
{
Augment();
printf("Yes\n%.2f\n", ans);
}
}
}