2017.7.5训练赛 反思加总结

蒟蒻面壁反思中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(xv2t)2+(v1t)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(xv2t)2+(v1t)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]:ii
d p [ i ] [ 1 ] : 长 度 为 i 且 第 i 朵 花 与 端 点 花 色 相 同 的 方 案 数 dp[i][1]:长度为i且第i朵花与端点花色相同的方案数 dp[i][1]:ii


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[i1][1](k1)+dp[i1][0](k2)
d p [ i ] [ 1 ] = d p [ i − 1 ] [ 0 ] ; dp[i][1] = dp[i-1][0]; dp[i][1]=dp[i1][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;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值