蒟蒻面壁反思中QwQ
总结:题目是不难的 但最近状态迷之感人
有简单算法的题第一想法都是暴力(如本场1002)
可暴力的题一直在想合适的算法优化(如本场1003)
虽然最近针对性地练了很多题 但做的都是提前已经知道算法的专题 跟比赛应该有的状态和做题思路都有很大差别。另外比赛策略确实有问题,总是喜欢跟榜做题,如果一道题很多人过掉而自己还没过就会感到慌张,甚至不会去思考其他本来已经有清晰方向的题。
虽然对最近的成绩很失望 但终究不想那么轻易地消沉下去
努力训练 继续刷题 不管最后结果如何 只要尽力了就没有遗憾了吧
Fighting ~!
1001:签到题
1002:地狱飞龙
题意:
最近clover迷上了皇室战争,他抽到了一种地狱飞龙,很开心。假设地域飞龙会对距离为d的敌人每秒造成k/d2伤害。假设地域飞龙位于坐标轴原点,以每秒v1的速度向y轴正方向移动,敌人在(x,0)的位置,以每秒v2的速度向x轴负方向移动。问,敌人至少有多少血量永远才不会被地狱飞龙喷死。(伤害是连续造成的,不是一秒一秒间断的)
Input
第一行为数据组数T(1<=T<=1000)
每组数据一行,包含4个实数,分别为v1,v2,x,k(1≤v1,v2,x,k≤10)。
Output
每组数据输出一行,为敌人最小血量,结果保留2为有效数字.
Sample Input
1
1 1 1 1
Sample Output
2.36
思路:
让人伤心的一道题,卡了两小时WA了26发。
一开始直接模拟做的,因为伤害是连续的,然后我就将每一秒分成1000份,单独求伤害,然后控制精度累加即可。然后精度设小就被卡精度,精度设大一点就超时,一直想找一个平衡点,但一直没有找到qwq
其实后来想到了从方程求解,设答案为
A
n
s
Ans
Ans
则 :
A
n
s
=
∑
t
=
0
∞
k
(
x
−
v
2
∗
t
)
2
+
(
v
1
∗
t
)
2
Ans = \sum_{t=0}^\infty \frac{k}{(x-v2*t)^2 + (v1*t)^2 }
Ans=t=0∑∞(x−v2∗t)2+(v1∗t)2k
但当时此题过了一片,加上前面的多次WA,心态有点爆炸,上学期高数97的我居然没看出这是积分的定义式(果然学得很差,只能应付考试),然后对该式无从下手的我又开始调之前模拟代码的精度平衡QwQ
但其实由积分“分割 取点 近似求和 取极限的思想“上面的等式就等于:
A n s = ∫ 0 ∞ k ( x − v 2 ∗ t ) 2 + ( v 1 ∗ t ) 2 d t Ans = \int_0^\infty \frac{k}{(x-v2*t)^2 + (v1*t)^2 }dt Ans=∫0∞(x−v2∗t)2+(v1∗t)2kdt
然后就成Simpson积分的裸题了
代码:
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#include<iostream>
using namespace std;
const double eps = 1e-8;
const double INF = 1e9 + 7;
double v1,v2,x,k;
double f(double t){
double res = (x-v2*t)*(x-v2*t) + (v1*t)*(v1*t);
return k/res;
}
double Simpson(double l,double r){
return (f(l) + f(r) + 4*f((l+r)/2.0)) * (r-l)/6.0;
}
double solve(double l,double r){
double mid = (l+r)/2.0;
if(fabs(Simpson(l,r) - Simpson(l,mid) - Simpson(mid,r))<eps)
return Simpson(l,mid) + Simpson(mid,r);
return solve(l,mid) + solve(mid,r);
}
int main(){
int T;scanf("%d",&T);
while(T--){
scanf("%lf%lf%lf%lf",&v1,&v2,&x,&k);
double ans = solve(0,INF);
printf("%.2f\n",ans);
}
return 0;
}
1003:魔法宝石
题意:
小s想要创造n种魔法宝石。小s可以用ai的魔力值创造一棵第i种魔法宝石,或是使用两个宝石合成另一种宝石(不消耗魔力值)。请你帮小s算出合成某种宝石的所需的最小花费。
Input
第一行为数据组数T(1≤T≤3)。
对于每组数据,首先一行为n,m(1≤n,m≤10^5)。分别表示魔法宝石种类数和合成魔法的数量。
之后一行n个数表示a1到an。(1≤ai≤10^9)。ai表示合成第i种宝石所需的魔力值。
之后m行,每行三个数a,b,c(1≤a,b,c≤n),表示一个第a种宝石和第b种宝石,可以合成一个第c种宝石。
Output
每组数据输出一行n个数,其中第i个数表示合成第i种宝石的魔力值最小花费。
Sample Input
1
3 1
1 1 10
1 2 3
Sample Output
1 1 2
思路:
读完题第一感觉是最短路,然后仔细一想此题是 u+v --> z 的 忽然没了建模的思路,但很明显存在多次更新的情况,即可能
x1 + y1 > z1,此时不能进行更新,但后面可能有操作
x2 + y2 < x1 使x1的值改变,然后进而使上面z1的值改变
当时的第一思路是 存下所有操作,然后遍历
每次成功更新之后,又从头开始遍历操作。
但复杂度感觉会很大,便开始思考操作与操作之间的顺序问题,想要将操作排序之后再进行遍历,但如何排序却一直没想出来,加上卡1002,最后已经没有时间去尝试此题。QWQ
赛后补掉1002后,对于此题先尝试了一下暴力多次循环更新操作
暴力循环10次…WA
暴力循环50次…卧槽…AC???!!!
50次居然就能AC…!!!???
!!!!???(黑人问号)
下次遇到题真的不要怕暴力,想不清楚先试一发,写暴力反正也才几分钟qwq
多么痛的领悟
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#include<iostream>
using namespace std;
typedef long long ll;
const int A = 1e5 + 10;
ll a[A];
ll x[A],y[A],z[A];
int main(){
int T;scanf("%d",&T);
while(T--){
int n,m;
scanf("%d%d",&n,&m);
for(int i=1 ;i<=n ;i++){
scanf("%I64d",&a[i]);
}
for(int i=1 ;i<=m ;i++){
scanf("%I64d%I64d%I64d",&x[i],&y[i],&z[i]);
}
for(int i=1 ;i<=50 ;i++){
for(int j=1 ;j<=m ;j++){
int u = x[j],v = y[j],w = z[j];
if(a[u] + a[v] < a[w]) a[w] = a[u] + a[v];
}
}
for(int i=1 ;i<=n ;i++){
printf("%I64d%c",a[i],i==n?'\n':' ');
}
}
return 0;
}
1004:签到题
1005: 某科学的打麻将
题意:
过年打麻将果然是一项必备技能(雾),打麻将的起手式是整理好自己手中的牌,现在你有十三张牌(只可能出现一万到九万,一筒到九筒,一条到九条),你要把这些牌整理好,使得相同花色的牌必须在连续的唯一一段(即所有的"万"要放在一起,所有的"条"要放在一起,所有的"筒"要放在一起。),每段内牌是按照从小到大的顺序排列(“万”,“条”,“筒”的顺序没有要求)。你每次只能将当前牌中的任意一张牌放到最左边或者最右边。请问最少经过多少次可以使得牌被整理好。
一万到九万,用数字1-9表示
一筒到九筒,用小写字母a-i表示
一条到九条,用大写字母A-I 表示
Input
第一行 一个数字T代表数据组数(T<=10000)
接下来每行 13个字符 代表初始的牌
Output
T行每行一个数字,代表答案
Sample Input
1
3abcABBDEFG11
Sample Output
1
Hint
样例解释
我们把3 放到 最右边就好了
abcABBDEFG113
思路:
对于全部为字母的情况,类似于51nod 1092
都是借助于LCS的思想来解决问题
但本题有三种不同类型的字符,因为相同类型的字符必须在一起
那么设三种不同类型分别为A,B,C
则排列组合有六种情况,分别与原串进行比较即可
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#include<string>
#include<iostream>
using namespace std;
typedef long long ll;
const int INF = 1e9 + 7;
const int A = 20;
string s[3],a,str;
int dp[A][A],len;
int check(){
int res = 0;
for(int i=1 ;i<=len ;i++){
for(int j=1 ;j<=len ;j++){
if(a[i-1] == str[j-1]) dp[i][j] = dp[i-1][j-1] + 1;
else dp[i][j] = dp[i][j-1];
res = max(res,dp[i][j]);
}
}
return len - res;
}
int main(){
ios::sync_with_stdio(false);
int T;cin >> T;
while(T--){
cin >> str;
len = str.length();
for(int i=0 ;i<3 ;i++) s[i] = "";
for(int i=0 ;i<len ;i++){
if(str[i]>='0' && str[i]<='9') s[0] += str[i];
else if(str[i]>='a' && str[i]<='z') s[1] += str[i];
else s[2] += str[i];
}
for(int i=0 ;i<3 ;i++) sort(s[i].begin(),s[i].end());
int ans = INF;
for(int i=0 ;i<3 ;i++){
a = s[(0+i)%3] + s[(1+i)%3] + s[(2+i)%3];
ans = min(ans,check());
}
for(int i=0 ;i<3 ;i++){
a = s[(0+i)%3] + s[(2+i)%3] + s[(1+i)%3];
ans = min(ans,check());
}
printf("%d\n",ans);
}
return 0;
}
1006:Hmz 的女装
题意:
Hmz为了女装,想给自己做一个长度为n的花环。现在有k种花可以选取,且花环上相邻花的种类不能相同。
Hmz想知道,如果他要求第l朵花和第r朵花颜色相同,做花环的方案数是多少。这个答案可能会很大,你只要输出答案对10^9+7取模的结果即可。
Input
第一行三个整数n,m,k(1≤n≤100000,1≤m≤100000,1≤k≤100000)
接下来m行,每行两个整数l,r,表示要求第l朵花和第r朵花颜色相同。保证l≠r且 |(r-l) mod n| ≠1.
Output
输出m行。对于每一个询问输出一个整数,表示做花环的方案数对10^9+7取模的结果。
Sample Input
8 3 2
1 4
2 6
1 3
8 3 3
1 4
2 6
1 3
Sample Output
0
2
2
60
108
132
思路:
N和M的范围是1e5,故对于每一个询问必须要在O(logN)或者O(1)的时间内来求解。
因为是环,直观上答案跟两个询问点的具体位置无关,而跟其距离有着密切的关系。
一共有k种颜色,若询问点为L,R
故L,R的颜色有k种情况,而对于每一种颜色color
我们可以看成花环从L,R两个点断开,形成了两条不同的且长度已知线段(均不包含L,R)
对于某一个特定的color
设 dp[x] :长度为x且两个端点均不为color的线段填色方案数
则若两个线段的长度分别为 len1,len2
则答案为:
A
n
s
=
d
p
[
l
e
n
1
]
∗
d
p
[
l
e
n
2
]
∗
k
Ans = dp[len1] * dp[len2] * k
Ans=dp[len1]∗dp[len2]∗k
而dp[len]的状态很难直接转移,故可升维来构造转移方程:
d
p
[
i
]
[
0
]
:
长
度
为
i
且
第
i
朵
花
与
端
点
花
色
不
同
的
方
案
数
dp[i][0]:长度为i且第i朵花与端点花色不同的方案数
dp[i][0]:长度为i且第i朵花与端点花色不同的方案数
d
p
[
i
]
[
1
]
:
长
度
为
i
且
第
i
朵
花
与
端
点
花
色
相
同
的
方
案
数
dp[i][1]:长度为i且第i朵花与端点花色相同的方案数
dp[i][1]:长度为i且第i朵花与端点花色相同的方案数
故
d
p
[
i
]
[
0
]
=
d
p
[
i
−
1
]
[
1
]
∗
(
k
−
1
)
+
d
p
[
i
−
1
]
[
0
]
∗
(
k
−
2
)
dp[i][0] = dp[i-1][1] * (k-1) + dp[i-1][0]*(k-2)
dp[i][0]=dp[i−1][1]∗(k−1)+dp[i−1][0]∗(k−2)
d
p
[
i
]
[
1
]
=
d
p
[
i
−
1
]
[
0
]
;
dp[i][1] = dp[i-1][0];
dp[i][1]=dp[i−1][0];
此题得解
代码:
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#include<iostream>
using namespace std;
typedef long long ll;
const int mod = 1e9 + 7;
const int A = 1e5 + 10;
ll dp[A][2];
int main(){
ll n,m,k;
while(~scanf("%I64d%I64d%I64d",&n,&m,&k)){
dp[1][0] = k-1;
dp[1][1] = 0;
for(int i=2 ;i<=n ;i++){
dp[i][0] = (dp[i-1][1] * (k-1) % mod + dp[i-1][0]*(k-2)%mod)%mod;
dp[i][1] = dp[i-1][0];
}
while(m--){
int l,r;
scanf("%d%d",&l,&r);
if(l>r) swap(l,r);
int x1 = (r-l-1),x2 = n - (r-l+1);
printf("%I64d\n",(dp[x1][0]*dp[x2][0])%mod * k%mod);
}
}
return 0;
}
1007:最大子段和
题意:
一个大小为n的数组a1到an(-104≤ai≤104)。请你找出一个连续子段,使子段长度为奇数,且子段和最大。
Input
第一行为T(1≤T≤5),代表数据组数。
之后每组数据,第一行为n(1≤n≤10^5),代表数组长度。
之后一行n个数,代表a1到an。
Output
每组数据输出一行,表示满足要求的子段和最大值。
Sample Input
1
4
1 2 3 4
Sample Output
9
思路:
预处理前缀和sum[i]
对于第i个前缀和 找出 1~(i-1)的最小前缀和,作差即可的最大子段和
此处要求长度为奇数
故可开两个优先队列,分别存第奇数个前缀和和第偶数个前缀和
然后一次遍历 先查询再插入即可
代码:
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#include<iostream>
#include<string>
#include<queue>
using namespace std;
typedef long long ll;
const int INF = 1e9 + 7;
const int A = 1e5 + 10;
int a[A],sum[A];
class
priority_queue<int> que1,que2;
int main(){
int T;scanf("%d",&T);
while(T--){
int n;scanf("%d",&n);
sum[0] = 0;
for(int i=1 ;i<=n ;i++){
scanf("%d",&a[i]);
sum[i] = sum[i-1] + a[i];
}
while(que1.size()) que1.pop();
while(que2.size()) que2.pop();
que2.push(0);
int ans = -INF;
for(int i=1 ;i<=n ;i++){
if(i&1){
int now = que2.top();
ans = max(ans,sum[i]+now);
que1.push(-sum[i]);
}
else{
int now = que1.top();
ans = max(ans,sum[i]+now);
que2.push(-sum[i]);
}
}
printf("%d\n",ans);
}
return 0;
}
1008:ch追妹
题意:
n个点的一张无向图,ch站在a点,ch要追的妹子站在b点。r_clover为了让ch安心训练,要阻止ch追妹。ch每走一步,r_clover就会挖断一条路。ch和r_clover均采用最优策略,问ch能不能追到妹子
Input
第一行为数据组数T(T≤10)。
每组数据的第一行为四个数 n,m,a,b(1≤a,b≤n≤20; 1≤m≤80),分别表示点数,边数,ch的位置,妹子的位置。
之后m行,每行两个数 u,v(1≤u,v≤n),表示u,v之间有一条无向边。数据保证没有重边和自环(即不会出现u到u的边,也不会出现两条u到v的边)。
Output
对每组数据输出一行,如果ch能够成功追妹,输出chhappy,否则输出chsad。
Sample Input
2
2 1 1 2
1 2
3 2 1 3
1 2
2 3
Sample Output
chhappy
chsad
思路:
先预处理妹子所在点(终点)到所有点的最短距离
再从起点进行搜索
对于某一个点u,若其可到达的v点数目(dis[v] < dis[u])比从起点到u点所用时间要少,即代表当ch走到u点时,对手早已经将它所有可以进一步靠近妹子的路径全部挖掉了
故代表此点必败。
否则 就继续向下面遍历:
若向下面遍历的每一个点均能到达终点,则说明必胜
代码:
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#include<iostream>
#include<string>
#include<queue>
using namespace std;
typedef long long ll;
const int INF = 1e9 + 7;
const int A = 100 + 10;
class Gra{
public:
int v,next;
}G[A];
int head[A],tot,dis[A],vis[A];
int n,m,a,b;
void add(int u,int v){
G[tot].v = v;
G[tot].next = head[u];
head[u] = tot++;
}
void get_dis(int u,int pre,int dep){
dis[u] = min(dis[u],dep);
if(++vis[u] > 5) return;
for(int i=head[u] ;i!=-1 ;i=G[i].next){
int v = G[i].v;
if(v == pre) continue;
get_dis(v,u,dep+1);
}
}
bool dfs(int u,int pre,int t){
//printf("u = %d pre = %d t = %d\n",u,pre,t);
if(u == b){
return true;
}
int cnt = 0;
for(int i=head[u] ;i!=-1 ;i=G[i].next){
int v = G[i].v;
if(v == pre) continue;
if(dis[v] < dis[u]) cnt++;
}
//printf("cnt = %d\n",cnt);
if(cnt <= t) return false;
bool flag = 1;
for(int i=head[u] ;i!=-1 ;i=G[i].next){
int v = G[i].v;
if(v == pre) continue;
if(dis[v] < dis[u])
if(dfs(v,u,t+1) == 0) flag = 0;
}
return flag;
}
int main(){
//freopen("input","r",stdin);
int T;scanf("%d",&T);
while(T--){
scanf("%d%d%d%d",&n,&m,&a,&b);
fill(dis,dis+A,INF);
memset(head,-1,sizeof(head));
tot = 0;
for(int i=1 ;i<=m ;i++){
int u,v;
scanf("%d%d",&u,&v);
add(u,v);add(v,u);
}
memset(vis,0,sizeof(vis));
get_dis(b,0,0);
if(dfs(a,0,0)) puts("chhappy");
else puts("chsad");
}
return 0;
}
1009:签到题
1010:爱看电视的LsF
题意:
LsF(刘师傅)非常喜欢看电视!
不幸的是,遥控器上的一些数字按钮坏了。 但他灵光一闪,如果他不能直接输入他想要看到的频道的号码,那么他可以先输入其他号码,再通过按下按钮 + 和 - (这两个按钮由24K钛合金制成,永远不会坏)的方式到达所需的频道。 按钮+将数字增加1,按钮-将数字减少1。当然他依然可以使用那些完好无损的数字按钮输入号码。
他最初在第S频道,他想看第T频道。他想知道由S到T频道所需的最少按钮按压次数。
Input
输入包含多组数据。
对于每组数据,第一行是三个整数n,S,T(n≤10,0≤S,T≤500,000。 第二行是n个数字 a1,a2,…,an,表示数字 ai键已经坏了 (0≤ai≤9,ai≠ajwheni≠j)。
Output
每组数据所需的最少按钮按压次数。
Sample Input
10 1 100
0 1 2 3 4 5 6 7 8 9
9 1 100
0 1 2 3 4 5 6 7 8
Sample Output
99
3
思路:
爆搜
求以下值距离终点的最小值:
1.起点值
2.通过按键可到达的所有比终点值小的最大值
3.通过按键可到达的所有比终点值大的最小值
代码:
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#include<iostream>
#include<string>
#include<queue>
using namespace std;
typedef long long ll;
const int INF = 1e9 + 7;
bool vis[15];
int ans,len,digit[15];
int n,S,T;
void dfs(int pos,int sum){
//printf("pos = %d sum = %d\n",pos,sum);
if(pos != len) ans = min(ans,abs(T-sum)+len-pos);
if(pos < 0){
return;
}
for(int i=0 ;i<=9 ;i++){
if(vis[i] == 0){
dfs(pos-1,sum*10+i);
}
}
}
int main(){
while(~scanf("%d%d%d",&n,&S,&T)){
memset(vis,0,sizeof(vis));
ans = abs(T-S);
for(int i=1 ;i<=n ;i++){
int x;scanf("%d",&x);
vis[x] = 1;
}
int tem = T;
len = 0;
while(tem>0){
digit[++len] = tem%10;
tem /= 10;
}
dfs(len,0);
printf("%d\n",ans);
}
return 0;
}