题目链接(PDF):https://cs.baylor.edu/~hamerly/icpc/qualifier_2019/naq2019.pdf
A Circuit Math
题解:后缀表达式,从左到右遍历表达式的每个数字和符号,遇到数字就进栈,遇到符号就将处于栈顶两个数字出栈,进行运算,并将运算结果进栈,直至栈空。
#include<bits/stdc++.h>
using namespace std;
int n;
int inp[30];
char ch;
stack<int> s;
string t;
int main(){
cin >> n;
for(int i = 0;i < n; i++){
cin >> ch;
if(ch=='T') inp[i] = 1;
else inp[i] = 0;
}
getchar();
getline(cin, t);
stringstream ss(t);
char tmp;
while(ss>>tmp){
if(tmp>='A' && tmp <= 'Z'){
s.push(inp[tmp-'A']);
}
else{
int a =s.top(); s.pop();
if(tmp=='*'){
int b = s.top(); s.pop();
s.push(a&b);
}
else if(tmp=='+'){
int b = s.top(); s.pop();
s.push(a|b);
}
else if(tmp=='-'){
s.push(1^a);
}
}
}
if(s.top()) cout << "T";
else cout << "F" ;
// system("pause");
return 0;
}
B Diagonal Cut
收获:若题干没有明确具体形式, 就按照最容易的形式计算,如本题,将每个小矩形按照正方形处理
题解:若当做正方形处理,则只需要看直线y =
M
x
N
\dfrac{Mx}{N}
NMx ((0,0)->(N,M))
是否经过正方形中心点即可。
正方形的左下角的点设为(p,q) 则其中心的点为(p+1/2, q+1/2)
即是否满足q +
1
2
\dfrac{1}{2}
21 =
M
N
\dfrac{M}{N}
NM
∗
*
∗(2p+1) 约分后得 2q+1 =
m
n
\dfrac{m}{n}
nm(2p + 1)
显然此时 m n互质 则只有两种情况
一. m n都是奇数
显然式子左边的必然是整数(奇数), 则式子右边也是整数(奇数 所以k也是奇数) 即(2p+1) = kn =》有多少个k符合题意
得 k =
2
p
n
\dfrac{2p}{n}
n2p +
1
n
\dfrac{1}{n}
n1 <
2
N
n
\dfrac{2N}{n}
n2N +
1
n
\dfrac{1}{n}
n1 且k为整数 所以k<=2
N
n
\dfrac{N}{n}
nN=2gcd(N,M) 又因为k是奇数所以k有gcd(N,M)个
二. m n中有一个是偶数
n(2q+1) = m(2p + 1) 显然该情况下 式中只有m或n是偶数 其余都是奇数 所以等式不成立
综上所述 return m%2 && n%2 ?gcd(M,N) : 0;
#include<bits/stdc++.h>
using namespace std;
typedef unsigned long long ull;
ull n, m;
ull gcd(ull a, ull b){
return b==0?a:gcd(b, a%b);
}
int main(){
scanf("%lld%lld",&n,&m);
ull d = gcd(n,m);
n /= d, m /= d;
if(n%2&&m%2) printf("%lld\n",d);
else printf("0\n");
//system("pause");
return 0;
}
C Gerrymandering
题解:签到题,就是题干有点长,理解题意就做出来了。
#include<bits/stdc++.h>
using namespace std;
int n, m, tmp;
int vote[1005][2]={0};
int a, u, v;
int main(){
int sum = 0;
ios::sync_with_stdio(false);
cin >> n >> m;
for(int i = 0; i < n; i++){
cin >> a >> u >> v;
vote[a][0] += u;
vote[a][1] += v;
sum += u+v;
}
int suma = 0, sumb = 0;
for(int i = 1; i <= m; i++){
int x, y;
if(vote[i][0] > vote[i][1]){
x = vote[i][0] - (vote[i][0]+vote[i][1])/2-1;
y = vote[i][1];
cout << "A";
}
else {
y = vote[i][1] - (vote[i][0]+vote[i][1])/2-1;
x = vote[i][0];
cout << "B";
}
cout << " " << x << " " << y << endl;
suma += x;
sumb += y;
}
// cout << sum << suma << sumb << endl;
printf("%.10f\n", abs(suma-sumb)*1.0/(sum*1.0));
// system("pause");
return 0;
}
D Missing Numbers
题解:签到题,在读取输入的同时,输出第i与i-1个数之间的数。最后,判断一下n与最后一个数是否相等
#include<bits/stdc++.h>
using namespace std;
int n;
int arr[205];
int cnt = 0;
int main(){
cin >> n;
arr[0] = 0;
for(int i = 1 ; i <= n; i++){
cin >> arr[i];
for(int j = arr[i-1]+1; j < arr[i]; j++)
cout << j << endl;
}
if(n==arr[n]) cout << "good job" << endl;
// system("pause");
return 0;
}
G Research Productivity Index
题解:概率dp,枚举提交k篇卷子,并计算出提交k篇的期望(等于结果*概率)。
用f(i,j)表示提交i份卷子,通过j份的函数值,由题干可得
f
(
i
,
j
)
=
i
i
j
f(i, j )=i^{\frac{i}{j}}
f(i,j)=iji(j>0);f(i,j)=0(j=0)。
用dp[i][j]表示提交i份卷子,通过j份的概率。并且从n份卷子选出k份卷子,显然当选出概率前k大的卷子时概率最大。
有
d
p
[
i
]
[
j
]
=
p
i
∗
d
p
[
i
−
1
]
[
j
−
1
]
+
(
1
−
p
i
)
∗
d
p
[
i
−
1
]
[
j
]
dp[i][j]=p_{i}*dp[i-1][j-1]+(1-p_{i})*dp[i-1][j]
dp[i][j]=pi∗dp[i−1][j−1]+(1−pi)∗dp[i−1][j]。则提交k份卷子的期望就等于
∑
0
k
\sum_0^k
∑0kf(k,j)*dp[k][j]。提交1-n份卷子的期望最大值即为答案。
#include<bits/stdc++.h>
using namespace std;
int n, tmp;
double inp[105];
double dp[105][105];
bool cmp(double a, double b){
return a>b;
}
int main(){
ios::sync_with_stdio(false);
cin >> n;
double ans = 0;
for(int i = 1; i <= n; i++){
cin >> tmp;
inp[i] = tmp*1.0/100;
}
sort(inp+1, inp+n+1, cmp);
//计算提交i份卷子,通过j份的概率
dp[0][0] = 1;
for(int i = 1; i <= n; i++){
dp[i][0] = dp[i-1][0]*(1-inp[i]);
}
for(int i = 1; i <= n; i++){
for(int j = 1; j <= n; j++){
dp[i][j] = dp[i-1][j-1]*inp[i] + dp[i-1][j]*(1-inp[i]);
}
}
//计算提交k份卷子的期望
for(int i = 1; i <= n; i++){
double res = 0;
for(int j = 1; j <= i; j++){
res += dp[i][j]*pow(j, j*1.0/(i*1.0));
}
ans = max(ans, res);
}
printf("%.9f\n", ans);
// system("pause");
return 0;
}
I Slow Leak
收获:弗洛伊德算法 这篇博客写的挺好的
用二维数组储存节点之间的距离,先将数组初始化((i,i)初始化0,其余初始化为∞)
然后根据输入 将(i,j)的值改为 输入的长度值k 即为任意两点之间的距离(无中转点)
核心算法:将1-n节点分别为中转点 改变两点之间的最短距离
for(int k = 1; k <= n; k++){//枚举中转点
for(int i = 1; i <= n; i++){
for(int j = 1; j <= n; j++){
if(e[i][j] > e[i][k] + e[k][j]){
e[i][j] =s e[i][k] + e[k][j];
}
}
}
}
题解:先用弗洛伊德算法 在完整图中计算出任意两点之间的最短距离
再建立一个只有起点、终点、修车店的图G’,将包含非G’节点的以及不符合题意的路径长度设为∞
再用一遍弗洛伊德算法算出起始点和终点满足题意的最短距离
#include<bits/stdc++.h>
using namespace std;
#define INF 1e18
typedef long long ll;
int n,m,t,d;
int rep[505];
ll table[505][505];//用于储存节点之间的距离
bool judge(int x){//判断是否为新图的节点 即起始点 修车店
return x==1 || x==n || rep[x];
}
void floid(){
for(int k = 1; k <= n; k++){
for(int i = 1; i <= n; i++){
for(int j = 1; j <= n; j++){
table[i][j] = min(table[i][j],table[i][k] + table[k][j]);
}
}
}
}
int main(){
scanf("%d%d%d%d",&n,&m,&t,&d);
for(int i = 1; i <= t; i++){
int x; scanf("%d",&x);
rep[x] = 1;//修车店的节点序号
}
//初始化
for(int i = 1; i <=n; i++){
for(int j = 1; j <= n; j++){
table[i][j] = INF;
}
}
int u, v, l;
for(int i = 1; i <= m; i++){
scanf("%d%d%d",&u,&v,&l);
table[u][v] = l;
table[v][u] = l;
}
//弗洛伊达算法计算出 任意两点之间的最短距离
floid();
//新图只包含起始点和修车店 即人要么在起点 要么在修车店
for(int i = 1; i <= n; i++){//将所有不满足题意的路径 设为无限大
for(int j = 1; j <= n; j++){
if(i!=j){
if(judge(i)&&judge(j)){//是新图节点
if(table[i][j]>d) table[i][j] = INF;//但是到不了
continue;
}
}
table[i][j] = INF;// i j不全是新图结点
}
}
//再用一遍弗洛伊达算法
floid();
//cout << table[2][n] << endl;
if(table[1][n]==INF) printf("stuck\n");
else printf("%d\n",table[1][n]);
//system("pause");
return 0;
}
J Stop Counting!
题解:答案要么来自前缀和,要么来自后缀和。
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e6+10;
int n, m;
double inp[maxn], pre[maxn]={0}, suf[maxn]={0};
int main(){
double ans = 0;
ios::sync_with_stdio(false);
cin >> n;
for(int i = 1; i <= n; i++){
cin >> inp[i];
pre[i] = pre[i-1] + inp[i];
ans = max(pre[i]/(i*1.0), ans);
}
for(int i=n; i>=1 ;i--){
suf[i] = suf[i+1] + inp[i];
ans = max(suf[i]/((n-i+1)*1.0), ans);
}
printf("%.9f\n", ans);
// system("pause");
return 0;
}
K Summer Trip
题解:暴力,不断枚举起点和终点,若起点和终点相同则结束枚举。虽然看着好像是O(n²),但实际上只有O(26*n)。对于每一次枚举,该串中字母均只出现过一次则count++
#include<bits/stdc++.h>
using namespace std;
int n;
int vis[30];
string t;
int ans = 0;
int main(){
cin >> t;
for(int i = 0; i < t.length()-1; i++){//枚举起点
memset(vis, 0, sizeof(vis));
for(int j = i+1; j<t.length(); j++){//枚举终点
if(t[j]==t[i]) break;
if(!vis[t[j]-'a']){
vis[t[j]-'a'] = 1;
ans++;
}
}
}
cout << ans << endl;
// system("pause");
return 0;
}
M Zipline
收获:开平方注意原值的正负; 出现除法 要考虑分母为0的情况(因为这个WA了)
题解:
初始化 g-=r, h-=r,看作是在地面上
最小值无可非议,两点间直线最短,题干中也明确提到是刚性绳(大概),所以不需要考虑松弛拉伸的情况。
最大值有两种求法
法一:对称 orz
任取一根柱子作关于ow的对称点P,连接另一个柱子的顶点形成直线l,l的长度就是最长距离,具体证明我忘了(小学知识orz)
#include<bits/stdc++.h>
using namespace std;
typedef unsigned long long ull;
int main(){
int t; cin>>t;
while(t--){
double w, g, r, h;
cin >> w>>g>>h>>r;
//if(g<h) swap(g,h);
g -= r, h -= r;
double mi = sqrt((g-h)*(g-h)+w*w);
double mx = sqrt((g+h)*(g+h)+w*w);
printf("%.8lf %.8lf\n",mi,mx);
}
return 0;
}
法二:求导
我当时的做法,设结点距离左柱子的距离为x,则最大值函数为
f(x) =
g
2
+
w
2
\sqrt{g^2+w^2}
g2+w2 +
h
2
+
(
w
−
x
)
2
\sqrt{h^2 + (w-x)^2}
h2+(w−x)2 ,只需求导等于零求出x即可得到答案(就不求二阶导严格证明了)
f’(x) =
x
g
2
+
w
2
\dfrac{x}{\sqrt{g^{2}+w^{2}}}
g2+w2x +
x
−
w
h
2
+
(
w
−
x
)
2
\dfrac{x-w}{\sqrt{h^2+(w-x)^2}}
h2+(w−x)2x−w
导数等于0移项化简开平方可得 x =
g
w
g
+
h
\dfrac{gw}{g+h}
g+hgw(分母不为0!!!) 再将x代入最大值函数即可求出答案
#include<bits/stdc++.h>
using namespace std;
typedef unsigned long long ull;
int main(){
int t; cin>>t;
while(t--){
double w, g, r, h;
cin >> w>>g>>h>>r;
if(g<h) swap(g,h);
g -= r, h -= r;
if(g==0&&h==0) printf("%.8lf %.8lf\n",w,w);
else{
double x = g*w/(g+h); // g x h w-x
double mi = sqrt((g-h)*(g-h)+w*w);
double mx = sqrt(g*g+x*x)+sqrt(h*h+(w-x)*(w-x));
printf("%.8lf %.8lf\n",mi,mx);
}
}
//system("pause");
return 0;
}
剩下的题 之后再补吧 orz