ACM备战刷题

快速幂

https://ac.nowcoder.com/acm/contest/548/B ,别人的讲解https://www.cnblogs.com/wmj6/p/10660232.html

m/n,求小数点后K1到K2位数字,若不足则用 0 补齐
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int inf=0x3f3f3f3f;
const ll mod=1e9+7;
ll m,n,k1,k2;
ll q_pow(ll a,ll n,ll mod){
    ll ans=m; ll t=a;
    while(n){
        if(n&1) ans=(ans*t)%mod;
        t=t*t%mod;
        n>>=1;
    }
        return ans;
}
int main(){
    int t;
    scanf("%d",&t);
    while(t--){
        scanf("%lld%lld%lld%lld",&m,&n,&k1,&k2);
        ll f=q_pow(10,k1-1,n);
        cout<<f<<endl;
        ll i=k1;
        while(1){
            f*=10;
            printf("%lld",f/n);
            f%=n;
            i++;
            if(i==k2+1)
            break;
        }
        printf("\n");
    }
}

尺取法

https://ac.nowcoder.com/acm/contest/13/E?&headNav=acm

/*输入

复制
8 1
1 1 1 2 2 3 2 2
输出65

复制
4*/
#include<bits/stdc++.h>
using namespace std;
const int maxn=100005;
int a[maxn];
map<int,int> mp;//
int main()
{
    int n,k;
    while(scanf("%d%d",&n,&k)!=EOF)
    {
        //q.clear();
        mp.clear();
        memset(a,0,sizeof(a));
        for(int i=1; i<=n; i++)
        {
            scanf("%d",&a[i]);
        }
        int l=1,r=1,ans=0;
        while(r<=n)
        {
            while(r<=n&&r-l-mp[a[l]]<=k)
            {

                mp[a[r]]++;
                r++;
            }
            ans=max(ans,mp[a[l]]);
           // mp[a[l++]]--;
           mp[a[l]]--;
           l++;
            while(l<=n&&a[l]==a[l-1])
            {
                mp[a[l]]--;
                l++;
            }
        }
        cout<<ans<<endl;

    }
}

https://vjudge.net/contest/292276#problem/D 2018北京网络赛

解题思路:/*有n个城市围成一圈,编号为1-n,到达第i个城市需要花费ai金币 同时获得bi金币起初有c金币,可以任意选择一个城市作为起点,
问能否从一个城市出发环游一圈?
用d[i]表示a[i]-b[i],如果小于0,说明不能从i这个点出发。因为要求编号最小,所以用l和r从1开始,r枚举l这个点能走到哪,
l从小到大枚举出发点,围成一圈可以通过开两倍数组,把前面复制到后面来解决。*/

一般面对这种围成一圈的,都可以开两倍数组,d[i+n]=d[i];在ans小于0时,应该是用while语句而不是if语句,直到某一个以l为开头的为正才符合要求。还有当l>n时,就说明每个点都枚举了一遍,没有符合要求的了,即-1.

/*There are n cities on a circle around the world which are numbered from 1 to n by their order on the circle. When you reach the city i at
the first time, you will get ai dollars (ai can even be negative), and if you want to go to the next city on the circle, you should
pay bi dollars. At the beginning you have c dollars.The goal of this game is to choose a city as start point,
 then go along the circle and visit all the city once, and finally return to the start point. During the trip, the money you have must
 be no less than zero.
 The first line of the input is an integer T (T ≤ 100), the number of test cases.
For each test case, the first line contains two integers n and c (1 ≤ n ≤ 106, 0 ≤ c ≤ 109).
 The second line contains n integers a1, …, an  (-109 ≤ ai ≤ 109), and the third line contains n integers b1, …, bn (0 ≤ bi ≤ 109).
It's guaranteed that the sum of n of all test cases is less than 106
Sample Input
2
3 0
3 4 5
5 4 3
3 100
-3 -4 -5
30 40 50
Sample Output
2
-1
Hint
For test case 1, both city 2 and 3 could be chosen as start point, 2 has smaller number. But if you start at city 1, you can't go anywhere.

For test case 2, start from which city seems doesn't matter, you just don't have enough money to complete a trip.*/


#include <iostream>
#include<cstdio>
#include<cstring>
using namespace std;
//map<int,int>a,b,d;
const int maxn=1e6+5;
int a[maxn],b[maxn],d[maxn];
int main()
{
    int T,n,c;
    cin>>T;

 
    for(int i=0; i<T; i++)
    {
      
       memset(a,0,sizeof(a));
       memset(b,0,sizeof(b));
       memset(d,0,sizeof(d));
        scanf("%d%d",&n,&c);
        int temp;
        for(int i=1; i<=n; i++)
        {
            scanf("%d",&temp);
            a[i]=temp;
        }
        for(int i=1; i<=n; i++)
        {
            scanf("%d",&temp);
            b[i]=temp;
            d[i+n]=d[i]=a[i]-b[i];
         
        }
        int l=1,r=1;
        long long  ans=c;
        while(l<=r&&r-l+1<=n)
        {
           ans+=d[r];
           r++;
           while(ans<0)
           {
               ans-=d[l];
               l++;
           }
        }
        if(l>n)
            cout<<"-1"<<endl;
        else
            cout<<l<<endl;
    }

    return 0;
}

BFS+DP

2018北京网赛A题——https://mp.csdn.net/postedit/89004191

题意:

给一个100×100的迷宫,’.’表示路面,’S’表示起点,’T’表示终点;’#’表示毒气区,进入毒气区必须要消耗一个氧气;’B’表示氧气区,每次进入自动获得一个氧气,可反复进入从而获得多个,但最多携带5个;’P’表示加速药,获得原理和氧气一样,使用后使下一次移动不耗时,可以无限携带。一次移动可以移动到相邻的四个格子,花费一个单位时间,如果移动到了毒气区,将在毒气区额外停留一个单位时间。求从S到T的最短时间,如果不能到达,输出-1。

思路:

dp[x][y][t]表示到达(x,y)且氧气瓶为t个时的最短时间,bfs根据每个点的特征处理每个点的最短时间(这里不用vis保存状态),最后在dp[ex][ey][0~5]里取最小值。

/*/*Input
There are no more than 25 test cases.

For each case, the first line includes two integers N and M(0 < N,M ≤ 100), meaning that the palace
is a N × M matrix.

Then the N×M matrix follows.

The input ends with N = 0 and M = 0.

Output
For each test case, print the minimum time (in minute) Sun Wukong needed to save Tang Monk.
If it's impossible for Sun Wukong to complete the mission, print -1

Sample Input
2 2
S#
#T
2 5
SB###
##P#T
4 7
SP.....
P#.....
......#
B...##T
0 0
Sample Output
-1
8
11*/
/*给一个100×100的迷宫,’.’表示路面,’S’表示起点,’T’表示终点;’#’表示毒气区,进入毒气区必须要消耗一个氧气;
’B’表示氧气区,每次进入自动获得一个氧气,可反复进入从而获得多个,但最多携带5个;’P’表示加速药,获得原理和氧气
一样,使用后使下一次移动不耗时,可以无限携带。一次移动可以移动到相邻的四个格子,花费一个单位时间,如果移动到了
毒气区,将在毒气区额外停留一个单位时间。求从S到T的最短时间,如果不能到达,输出-1。*/
/*用dp[x][y][t]表示到达(x,y)且氧气瓶为t个时的最短时间,bfs根据每个点的特征处理每个点的最短时间
(这里不用vis保存状态),
最后在dp[ex][ey][0~5]里取最小值。*/
#include <iostream>
#include<bits/stdc++.h>
using namespace std;
const int inf=0x3f3f3f3f;
char a[105][105];
int sx,sy,ex,ey;
struct node
{
    int x,y,num;
    node(int i,int j,int k):x(i),y(j),num(k) {}
};
int dp[105][105][6];
int dir[][2]= {{0,1},{0,-1},{-1,0},{1,0}};
int n,m;
int bfs()
{
    queue<node> q;
    node aa=node(sx,sy,0);

    memset(dp,inf,sizeof(dp));
    dp[sx][sy][0]=0;
    q.push(aa);
    while(!q.empty())
    {
        node t=q.front();
        q.pop();
        for(int i=0; i<4; i++)
        {
            int newx=t.x+dir[i][0];
            int newy=t.y+dir[i][1];
            if(newx>=0&&newx<n&&newy>=0&&newy<m)
            {
                if((a[newx][newy]=='S'||a[newx][newy]=='.')&&dp[newx][newy][t.num]>dp[t.x][t.y][t.num]+1)
                {
                    dp[newx][newy][t.num]=dp[t.x][t.y][t.num]+1;
                    q.push(node(newx,newy,t.num));
                }
                else  if((a[newx][newy]=='#')&&t.num>0&&dp[newx][newy][t.num-1]>dp[t.x][t.y][t.num]+2)
                {
                    dp[newx][newy][t.num-1]=dp[t.x][t.y][t.num]+2;
                    q.push(node(newx,newy,t.num-1));
                }
                else  if(a[newx][newy]=='P'&&dp[newx][newy][t.num]>dp[t.x][t.y][t.num])
                {
                    dp[newx][newy][t.num]=dp[t.x][t.y][t.num];
                    q.push(node(newx,newy,t.num));
                }
            else if (a[newx][newy]=='B'&&t.num<5&&dp[newx][newy][t.num+1]>dp[t.x][t.y][t.num]+1)
            {
                dp[newx][newy][t.num+1]=dp[t.x][t.y][t.num]+1;
                     q.push(node(newx,newy,t.num+1));

                }
            else if (a[newx][newy]=='B'&&t.num==5&&dp[newx][newy][t.num]>dp[t.x][t.y][t.num]+1)
            {
                dp[newx][newy][t.num]=dp[t.x][t.y][t.num]+1;
                     q.push(node(newx,newy,t.num));

                }
                else if(a[newx][newy]=='T'&&dp[newx][newy][t.num]>dp[t.x][t.y][t.num]+1)
                {
                    dp[newx][newy][t.num]=dp[t.x][t.y][t.num]+1;
                    q.push(node(newx,newy,t.num));
                }
            }
        }
    }
    int ans=inf;
    for(int i=0;i<6;i++)
    {
        ans=min(ans,dp[ex][ey][i]);
    }
    if(ans!=inf)
        return ans;
    return -1;
}
int main()
{
    //int n,m;
    while(~scanf("%d%d",&n,&m)&&n&&m)
    {
        memset(a,0,sizeof(a));
        for(int i=0; i<n; i++)
        {
            scanf("%s",a[i]);
        }
        for(int i=0; i<n; i++)
        {
            for(int j=0; j<m; j++)
            {
                if(a[i][j]=='S')
                {
                    sx=i;
                    sy=j;
                }
                else if(a[i][j]=='T')
                {
                    ex=i;
                    ey=j;
                }
            }
        }
        cout<<bfs()<<endl;
    }
    return 0;
}

DFS

2018北京网赛https://vjudge.net/contest/292276#problem/B

题意:给n个首位连接的字符串,找这n个字符串环中的最长公共子串。

思路:因为数据量和字符串长度很小,可以用dfs枚举出每个字符串的所有子串,用set保存。至于字符串环的处理,考虑每次枚举出的字符串可以从0到sz(此字符串的长度)枚举分成两半,再把后面的部分加到前面即可构造出所有的环形字符串。

int sz=t.length();
        for(int j=0;j<sz;j++)
        {
            st[x].insert(t.substr(j,sz-j)+t.substr(0,j));
        }

此地方是这个代码的精髓所在我认为。太巧妙了,这样就可以充分考虑到是个环,也能充分插入以每一个开头的字符串了。

/*Input
There are no more than 10 test cases.

In each case:

The first line is an integer n, meaning there are n (0 < n ≤ 10) arm rings.

Then n lines follow. Each line is a string on an arm ring consisting of only lowercase letters.
The length of the string is no more than 8.

Output
For each case, print the password. If there is no LCS, print 0 instead.

Sample Input
2
abcdefg
zaxcdkgb
5
abcdef
kedajceu
adbac
abcdef
abcdafc
2
abc
def
Sample Output
acdg
acd
0*/
/*因为数据量和字符串长度很小,可以用dfs枚举出每个字符串的所有子串,用set保存。至于字符串环的处理,
考虑每次枚举出的字符串可以从0到sz(此字符串的长度)枚举分成两半,再把后面的部分加到前面即可构造出所有的环形字符串。*/

#include <iostream>
#include<bits/stdc++.h>
using namespace std;
int n,len;
set<string> st[12];
string s,t;
void dfs(int x, int l)
{
    if(l==len)
    {
        if(t=="")
            return;
        int sz=t.length();
        for(int j=0;j<sz;j++)
        {
            st[x].insert(t.substr(j,sz-j)+t.substr(0,j));
        }
        return ;

    }
    t+=s[l];
    dfs(x,l+1);//要选l开头的
    t.erase(t.end()-1);
    dfs(x,l+1);//不选l开头的
}
int main()
{
    while(scanf("%d",&n)!=EOF)
    {
        for(int i=0;i<n;i++)
        {
            st[i].clear();
        }
        for(int i=0;i<n;i++)
        {
            cin>>s;
            len=s.length();
            t="";
            dfs(i,0);
        }
        string ans;
        set<string>::iterator it=st[0].begin();
        for(;it!=st[0].end();it++)
        {
            int flag=0;
            for(int j=1;j<n;j++)
            {
                // if(!st[j].count(*it))
                if(st[j].find(*it)==st[j].end())
                {
                    flag=1;
                    break;
                }
            }
             if(!flag)
            {
                if((*it).length()>ans.length())   ans=*it;
                else if((*it).length()==ans.length()&&(*it)<ans)    ans=*it;
            }
        }

         if(ans=="")
            cout<<0<<endl;
        else
            cout<<ans<<endl;
    }
    return 0;
}

模拟+输出格式控制

https://vjudge.net/contest/292530#problem/K2018女生专场

感想:题目要看清别人说的格式是怎么的,cout<<left<<setw(4)<<......;这种格式我还第一次见,学到了

我不会居中输出,(因为我只想着怎么用setw了),可以考虑先输出几个空格,再来设置对齐方式。

我开始想的是用getline(cin,s),然后用空格分开,这种方法我想复杂了,直接用cin不就分开了吗。主要是想复杂了,也做错了,我再去看看为什么会错吧。

错误找到啦,一定要谨记string定义的字符串不能直接索引赋值

/*Input
第一行包含一个正整数T(1≤T≤1000)T(1≤T≤1000),表示评测记录的数量。

接下来TT行,每行首先是一个正整数rank(1≤rank≤400)rank(1≤rank≤400),表示队伍的排名。

接下来一个长度不超过1616的字符串SS,表示队名,SS仅由大小写字母、数字以及下划线"_"组成。

接下来一个正整数prob(1001≤prob≤1013)prob(1001≤prob≤1013),表示题号。

接下来一个字符串T(T∈{Running,AC,WA,TLE,MLE,RTE,CE,OLE,PE,FB})T(T∈{Running,AC,WA,TLE,MLE,RTE,CE,OLE,PE,FB}),
表示评测状态,除RunningRunning外均表示评测结束。若为RunningRunning,则还会输入一个正整数p(1≤p≤9)p(1≤p≤9),
表示已经通过了p×10%p×10%的测试点。若为FBFB,则表示全场第一个通过该题,应显示AC∗AC∗。
Output
对于每条评测记录,按要求输出一行一个长度为3838的字符串,即直播显示效果。
Sample Input
5
19 qqqqq_University 1001 Running 3
125 quailty_U_2 1002 WA
4 quailty_U_3 1003 TLE
1 quailty_U_4 1003 FB
2 qqqqq 1001 AC
Sample Output
 19|qqqqq_University|1001|[XXX       ]
125|quailty_U_2     |1002|[    WA    ]
  4|quailty_U_3     |1003|[    TLE   ]
  1|quailty_U_4     |1003|[    AC*   ]
  2|qqqqq           |1001|[    AC    ]*/
#include <iostream>
#include<bits/stdc++.h>
using namespace std;
int ranks,tihao;
string duinum,status;
int main()
{
    int T;
    cin>>T;
    for(int i=0;i<T;i++)
    {
        cin>>ranks>>duinum>>tihao>>status;
        int flag=0;
        int num;
        if(status=="Running")
        {

            cin>>num;
            flag=1;
        }
        cout<<right<<setw(3)<<ranks;
        cout<<'|'<<left<<setw(16)<<duinum<<'|';
        cout<<tihao<<"|[";
        if(flag==1)
        {
            string ss="";
            for(int j=0;j<num;j++)
            {
                ss+="X";
            }
            cout<<left<<setw(10)<<ss<<"]"<<endl;
        }
        else
        {
            if(status=="FB")
            {
                cout<<"    "<<left<<setw(6)<<"AC*"<<"]"<<endl;
            }
            else
            {
                cout<<"    "<<left<<setw(6)<<status<<"]"<<endl;
            }

        }
    }
    return 0;
}

算法优化

后缀数组比大小https://vjudge.net/contest/292530#problem/H

题意:设sufi表示以i为开始的后缀,即S[i..n]。请对每个i(1≤i<n),判断sufi和sufi+1的字典序大小关系。 

解答:我开始想的是每个索引的字母相比即可得到答案。然而当这个索引的字母和下个索引的字母相同时,不能直接看出。于是我想到了每当有这个情况发生时,就写一个for循环,直到有不相等的情况,输出答案。然而,由于我是每个左寅每个索引的算,这样就会在算下一个索引时,执行与刚才同样的步骤,这样就会超时啊。所以以后超时的时候,要考虑自己的代码复杂度,是否是有很多情况重复计算了

还有就是用string比char[]慢很多。

/*Input
第一行包含一个正整数T(1≤T≤10)T(1≤T≤10),表示测试数据的组数。

每组数据第一行包含一个正整数n(2≤n≤1000000)n(2≤n≤1000000),表示字符串SS的长度。

第二行包含一个长度为nn的小写字符串SS。
Output
对于每组数据,输出一行n−1n−1个字符,第ii个字符表示sufisufi和sufi+1sufi+1的大小关系,若sufi<sufi+1sufi<sufi+1,
输出<<,否则输出>>,显然不存在相等关系。
Sample Input
1
17
quailtyacmbestacm
Sample Output
<><<<<><<><<<><<*/
/*2
12
bbcbaabcccba
2
cb
*/
#include<bits/stdc++.h>
using namespace std;
char S[int(1e6+5)] ;
int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        int n;
        scanf("%d",&n);
        scanf("%s",S);
        for(int i=0;i<n-1;i++)
        {
            int pos=i;
            while(S[pos]==S[pos+1]&&pos<n-2)
                pos++;
            if(S[pos]<S[pos+1])
            {
                for(int j=0;j<pos-i+1;j++)
                {
                    printf("<");
                }
            }
            else if(S[pos]>S[pos+1])
            {
                for(int j=0;j<pos-i+1;j++)
                {
                    printf(">");
                }
            }
           else if(pos==n-2)
            {
                for(int j=0;j<pos-i+1;j++)
                    printf(">");
            }
            i=pos;

        }
        printf("\n");
    }
}

唯一分解定理

口算训练:https://vjudge.net/contest/292530#problem/A

这题正解是要对每一个数分解质因数,由唯一分解定理可知,任何一个数可拆成唯一的素数的幂之积。举个例子,24=2^3*3,6=2*3,因为24的2和3的个数都能凑出6=2*3来,所以24是6的倍数。预处理用二维向量,一维表示因子,另一维表示能拆成此因子的数的下标。查询的时候看d的每个因子所在的区间数量是否能凑够,upper_bound找第一个大于r的位置,lower_bound找第一个大于等于l的位置,相减即为此区间d的这个因子的个数。

#include<bits/stdc++.h>
using namespace std;
#define inf 0x3f3f3f3f
#define ll long long
const int N=1e5+5;
const int mod=1e9+7;
const double eps=1e-8;
const double PI = acos(-1.0);
#define lowbit(x) (x&(-x))
vector<int> g[N];
int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
        int n,m;
        scanf("%d%d",&n,&m);
        for(int i=0;i<N;i++)
            g[i].clear();
        for(int i=1; i<=n; i++)
        {
            int x;
            scanf("%d",&x);
            int sx=sqrt(x);
            for(int j=2;j<=sx; j++)
            {
                while(x!=1&&x%j==0)
                {
                    g[j].push_back(i);//i是下标,j是因子
                    x/=j;
                }
                if(x==1)
                    break;
            }
            if(x>1)//eg:x=7,sx=2,
            {
                g[x].push_back(i);//切记i是下标
            }
        }
        for(int i=0;i<m;i++)
        {
            int l,r,d;
            scanf("%d%d%d",&l,&r,&d);
            int flag=0;
            for(int j=2;j*j<=d;j++)
            {
                int cnt=0;
                while(d!=1&&d%j==0)
                {
                    cnt++;
                    d/=j;
                }
                if(cnt>upper_bound(g[j].begin(),g[j].end(),r)-lower_bound(g[j].begin(),g[j].end(),l))
                //这样相减就是l到r之间j因子的个数,只有大于了cnt,才会满足要求,否则就不满足。
                {
                    flag=1;
                    break;
                }
                if(d==1)
                    break;
            }
            if(d>1&&!flag&&!(upper_bound(g[d].begin(),g[d].end(),r)-lower_bound(g[d].begin(),g[d].end(),l)))
            {
                flag=1;
            }
            if(!flag)
                puts("Yes");
            else
                puts("No");
        }
    }
    return 0;
}

二分的应用

缺失的数据:https://vjudge.net/contest/292530#problem/B

这道题题目意思简单,但想A掉的话不简单。有特别多的注意点,首先n^a会爆,不能那样直接算,而是用k来除;其次,直接用log来求的话,会有精度误差,所以可以定义一个s[]数组来存2的N次方,注意还要用long double,不然会有精度误差,C++要用long double,java的double也不够用,所以用log(x)/log(2)再向上取整就会有误差,可以通过枚举出2的n次方来解决。

/*著名出题人小Q出过非常多的题目,在这个漫长的过程中他发现,确定题目的数据范围是非常痛苦的一件事。

每当思考完一道题目的时间效率,小Q就需要结合时限以及评测机配置来设置合理的数据范围。

因为确定数据范围是一件痛苦的事,小Q出了非常多的题目之后,都没有它们设置数据范围。对于一道题目,小Q会告诉你他的算法的时间复杂度为O(nalogbn),且蕴含在这个大O记号下的常数为1。同时,小Q还会告诉你评测机在规定时限内可以执行k条指令。小Q认为只要na(⌈log2n⌉)b不超过k,那么就是合理的数据范围。其中,⌈x⌉表示最小的不小于x的正整数,即x上取整。

自然,小Q希望题目的数据范围n越大越好,他希望你写一个程序帮助他设置最大的数据范围。


Input
第一行包含一个正整数T(1≤T≤1000),表示测试数据的组数。

每组数据包含一行三个正整数a,b,k(1≤a,b≤10,106≤k≤1018),分别描述时间复杂度以及允许的指令数。


Output
对于每组数据,输出一行一个正整数n,即最大可能的n。


Sample Input
3
1 1 100000000
2 1 100000000
1 3 200000000


Sample Output
4347826
2886
48828
*/
#include<bits/stdc++.h>
typedef long long ll;
using namespace std;
ll a,b,k;
ll s[100]={1};
int check(ll n)
{
    int j=0;
    for(j=0;j<=62;j++)
        if(n<=s[j])break;
    long double t=k;
    for(int i=0;i<b;i++)
    {
        t=t/(long double)j;
        if(t<1.0)return 0;
    }
    for(int i=0;i<a;i++)
    {
        t=t/(long double)n;
        if(t<1.0)return 0;
    }
    return 1;
}
int main()
{
    int T;
    scanf("%d",&T);
    for(int i=1;i<=62;i++)
    {
        s[i]=ll(1ll<<i);//1ll是把1转化为longlong,因为1是int型
       // cout<<s[i]<<endl;//s[i]存的即是2的i次方
    }
    while(T--)
    {
	  scanf("%lld%lld%lld",&a,&b,&k);
	  ll l=0,r=1e18,mid;
	  ll ans=0;
	 
	  while(l<=r)
      {
          mid=(l+r)/2;
          if(check(mid))
          {
              ans=mid;
          
           l=mid+1;
          }
          else r=mid-1;
      }
      printf("%lld\n",ans);
      
     /*if(check(r))printf("%lld\n",r);
      else printf("%lld\n",l);*/
    }
	return 0;
}

Dijkstra+堆优化

hdu6290 奢侈的旅行https://vjudge.net/contest/292530#problem/D

 通过公式可以看出,level越大,越不容易超过bi,则越不容易经过道路。所以用了排序。由于priority_queue的作用与sort相反,所以在写比较函数时,写的是大于负号,实则排序是按照从小到大排序的。这道题的解析还有亮点,就是采用了结构体,我自己写的时候一直不知道怎么初始化,不知道怎么建立这些变量之间的关系。现在看懂了,真的厉害了。

 

/*高玩小Q不仅喜欢玩寻宝游戏,还喜欢一款升级养成类游戏。在这个游戏的世界地图中一共有n个城镇,编号依次为1到n。

这些城镇之间有m条单向道路,第i 条单项道路包含四个参数ui,vi,ai,bi,表示一条从ui号城镇出发,在vi号城镇结束的单向道路,
因为是单向道路,这不意味着小Q可以从vi沿着该道路走到ui。小Q的初始等级level为1,每当试图经过一条道路时,需要支付
cost=log2level+ailevel点积分,并且经过该道路后,小Q的等级会提升ai级,到达level+ai级。但是每条道路都会在一定意义上歧视低消费玩家
,准确地说,如果该次所需积分cost<bi,那么小Q不能经过该次道路,也不能提升相应的等级。

注意:本游戏中等级为正整数,但是积分可以是任意实数。

小Q位于1号城镇,等级为1,现在为了做任务要到n号城镇去。这将会是一次奢侈的旅行,请写一个程序帮助小Q找到需要支付的总积分最少的
一条路线,或判断这是不可能的。


Input
第一行包含一个正整数T(1≤T≤30),表示测试数据的组数。

每组数据第一行包含两个整数n,m(2≤n≤100000,1≤m≤200000),表示城镇数和道路数。

接下来m行,每行四个整数ui,vi,ai,bi(1≤ui,vi≤n,ui≠vi,0≤ai≤109,0≤bi≤60),分别表示每条单向道路。


Output
对于每组数据,输出一行一个整数,即最少所需的总积分的整数部分,如:4.9999输出4,1.0输出1。若不存在合法路线请输出−1。


Sample Input
1
3 3
1 2 3 2
2 3 1 6
1 3 5 0


Sample Output
2*/
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1000050;
const long long INF = 1ll<<61;  //这里一开始用了0x3f3f3f3f...sb了

struct qnode
{
    int v;
    long long c;//c就是代表等级
    qnode(int v=0,long long c=0):v(v),c(c){}
    bool operator <(const qnode &r)const
    {
        return c>r.c;
    }
};
struct Edge
{
    int to;//to即是v
    int w,MIN;//w即是ai,MIN即是bi
    Edge(int to,int w,int MIN):to(to),w(w),MIN(MIN){}
};

vector<Edge>E[maxn];
bool vis[maxn];
long long dis[maxn];

void Dijstra(int n,int start)
{
    memset(vis,false,sizeof(vis));    //nima,一开始赋值成-1了....
    for(int i=1;i<=n;i++)
        dis[i] = INF;//dis[i]相当于level
    priority_queue<qnode> que;//
     while(!que.empty()) que.pop();
   dis[start] = 1;//初始等级为1
    que.push(qnode(start,1));
    qnode tmp;
    while(!que.empty())
    {
        tmp = que.top();//当前节点
        que.pop();
        int u = tmp.v;//当前节点的编号
        if(vis[u])
            continue;
        vis[u] = 1;
        for(int i=0;i<E[u].size();i++)
        {
            int v = E[u][i].to;//当前节点的“对象”,可到达的那一边
            int a = E[u][i].w;//到达那边可增加的等级
           //if()
        if(log2((dis[u]+a*1.0) / dis[u]*1.0)<E[u][i].MIN)
            //if((a/dis[u])<E[u][i].MIN)     //这里不乘1.0也可以
                continue;
            if(!vis[v]&&dis[v]>dis[u]+a)   //这里的!VIS重要
            {
                dis[v] = dis[u] + a;
                que.push(qnode(v,dis[v]));
            }
        }
    }
    if(dis[n]==INF)
        printf("-1\n");
    else
        printf("%d\n",(int)log2(dis[n]*1.0));
}

int main()
{
    int t,m,u,v,n;
    int a,b;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;i++)
            E[i].clear();
        for(int i=0;i<m;i++)
        {
            scanf("%d%d%d%d",&u,&v,&a,&b);
            //b=(1ll<<b)-1;
            E[u].push_back(Edge(v,a,b));
        }
        Dijstra(n,1);
    }
    return 0;
}

线段树

dalao的总结https://www.cnblogs.com/ya-cpp/p/4165777.html

线段树模板 :对点进行增减,对区间进行查询https://vjudge.net/problem/HDU-1166

注意函数调用时实参的用法,还有就是如果是用char定义的数组,不能够直接与一个字符串比较,可用s[0]='E';

/*Input
第一行一个整数T,表示有T组数据。
每组数据第一行一个正整数N(N<=50000),表示敌人有N个工兵营地,接下来有N个正整数,第i个正整数ai代表第i个工兵营地里开始时有ai个人(1<=ai<=50)。
接下来每行有一条命令,命令有4种形式:
(1) Add i j,i和j为正整数,表示第i个营地增加j个人(j不超过30)
(2)Sub i j ,i和j为正整数,表示第i个营地减少j个人(j不超过30);
(3)Query i j ,i和j为正整数,i<=j,表示询问第i到第j个营地的总人数;
(4)End 表示结束,这条命令在每组数据最后出现;
每组数据最多有40000条命令

Output
对第i组数据,首先输出“Case i:”和回车,
对于每个Query询问,输出一个整数并回车,表示询问的段中的总人数,这个数保持在int以内。

Sample Input
1
10
1 2 3 4 5 6 7 8 9 10
Query 1 3
Add 3 6
Query 2 7
Sub 10 2
Add 6 3
Query 3 10
End

Sample Output
Case 1:
6
33
59*/
#include<bits/stdc++.h>
using namespace std;
#define maxn 100007  //元素总个数
#define ls l,m,rt<<1
#define rs m+1,r,rt<<1|1
int Sum[maxn<<2],Add[maxn<<2];//Sum求和,Add为懒惰标记
int A[maxn],n;//存原数组数据下标[1,n]
//(1)建树:
//PushUp函数更新节点信息 ,这里是求和
void PushUp(int rt)
{
    Sum[rt]=Sum[rt<<1]+Sum[rt<<1|1];
}
//Build函数建树
void Build(int l,int r,int rt)  //l,r表示当前节点区间,rt表示当前节点编号
{
    if(l==r)  //若到达叶节点
    {
        Sum[rt]=A[l];//储存数组值
        return;
    }
    int m=(l+r)>>1;
    //左右递归
    Build(l,m,rt<<1);
    Build(m+1,r,rt<<1|1);
    //更新信息
    PushUp(rt);
}
//(2)点修改:
//假设A[L]+=C:
void Update(int L,int C,int l,int r,int rt) //l,r表示当前节点区间,rt表示当前节点编号
{
    if(l==r) //到叶节点,修改
    {
        Sum[rt]+=C;
        return;
    }
    int m=(l+r)>>1;
    //根据条件判断往左子树调用还是往右
    if(L <= m)
        Update(L,C,l,m,rt<<1);
    else
        Update(L,C,m+1,r,rt<<1|1);
    PushUp(rt);//子节点更新了,所以本节点也需要更新信息
}
//    首先是下推标记的函数:
void PushDown(int rt,int ln,int rn)
{
    //ln,rn为左子树,右子树的数字数量。
    if(Add[rt])
    {
        //下推标记
        Add[rt<<1]+=Add[rt];
        Add[rt<<1|1]+=Add[rt];
        //修改子节点的Sum使之与对应的Add相对应
        Sum[rt<<1]+=Add[rt]*ln;
        Sum[rt<<1|1]+=Add[rt]*rn;
        //清除本节点标记
        Add[rt]=0;
    }
}
//(3)区间修改:
//假设A[L,R]+=C
void Update(int L,int R,int C,int l,int r,int rt) //L,R表示操作区间,l,r表示当前节点区间,rt表示当前节点编号
{
    if(L <= l && r <= R) //如果本区间完全在操作区间[L,R]以内
    {
        Sum[rt]+=C*(r-l+1);//更新数字和,向上保持正确
        Add[rt]+=C;//增加Add标记,表示本区间的Sum正确,子区间的Sum仍需要根据Add的值来调整
        return ;
    }
    int m=(l+r)>>1;
    PushDown(rt,m-l+1,r-m);//下推标记
    //这里判断左右子树跟[L,R]有无交集,有交集才递归
    if(L <= m)
        Update(L,R,C,l,m,rt<<1);
    if(R >  m)
        Update(L,R,C,m+1,r,rt<<1|1);
    PushUp(rt);//更新本节点信息
}
//    (4)区间查询:
//    询问A[L,R]的和

//然后是区间查询的函数:
int Query(int L,int R,int l,int r,int rt) //L,R表示操作区间,l,r表示当前节点区间,rt表示当前节点编号
{
    if(L <= l && r <= R)
    {
        //在区间内,直接返回
        return Sum[rt];
    }
    int m=(l+r)>>1;
    //下推标记,否则Sum可能不正确
    PushDown(rt,m-l+1,r-m);
    //累计答案
    int ANS=0;
    if(L <= m)
        ANS+=Query(L,R,l,m,rt<<1);
    if(R >  m)
        ANS+=Query(L,R,m+1,r,rt<<1|1);
    return ANS;
}
/*(5)函数调用:
//建树
Build(1,n,1);
//点修改
Update(L,C,1,n,1);
//区间修改
Update(L,R,C,1,n,1);
//区间查询
int ANS=Query(L,R,1,n,1);*/
/*(1) Add i j,i和j为正整数,表示第i个营地增加j个人(j不超过30)
(2)Sub i j ,i和j为正整数,表示第i个营地减少j个人(j不超过30);
(3)Query i j ,i和j为正整数,i<=j,表示询问第i到第j个营地的总人数;*/

char fu[10];
int main()
{
    int T;
    scanf("%d",&T);
    for(int i=1;i<=T;i++)
    {
        memset(Sum,0,sizeof(Sum));
        memset(Add,0,sizeof(Add));
        memset(A,0,sizeof(A));

        int n;
        scanf("%d",&n);
        int flag=0;
        for(int p=1;p<=n;p++)
        {
            scanf("%d",&A[p]);
        }
        Build(1,n,1);
         printf("Case %d:\n",i);
        //while(scanf("%s",fu)!=EOF&&fu!="End")
        while(scanf("%s",fu)!=EOF&&fu[0]!='E')
        {

            int x,y;
            scanf("%d%d",&x,&y);
            char c=fu[0];
            if(c=='Q')
            {
                long long ANS=Query(x,y,1,n,1);
                printf("%lld\n",ANS);
            }
            else if(c=='A')//点修改,第
            {
                Update(x,y,1,n,1);
            }
            else if(c=='S')
            {
                Update(x,-y,1,n,1);
            }
        }

    }
}

线段树

线段树功能:update:单点替换 query:区间最值

hdu1754https://vjudge.net/problem/HDU-1754

在使用%c时一定要注意,当出问题时,可以通过在%c前加空格,或加getchar(0,或在之后加空格等。都试试就好,每个题不一样。虽然题虽然也类似模板题,但也要自己改一下函数,这应该是比赛常考的吧

/*很多学校流行一种比较的习惯。老师们很喜欢询问,从某某到某某当中,分数最高的是多少。
这让很多学生很反感。

不管你喜不喜欢,现在需要你做的是,就是按照老师的要求,写一个程序,模拟老师的询问。当然,老师有时候需要
更新某位同学的成绩。
Input
本题目包含多组测试,请处理到文件结束。
在每个测试的第一行,有两个正整数 N 和 M ( 0<N<=200000,0<M<5000 ),分别代表学生的数目和操作的数目。
学生ID编号分别从1编到N。
第二行包含N个整数,代表这N个学生的初始成绩,其中第i个数代表ID为i的学生的成绩。
接下来有M行。每一行有一个字符 C (只取'Q'或'U') ,和两个正整数A,B。
当C为'Q'的时候,表示这是一条询问操作,它询问ID从A到B(包括A,B)的学生当中,成绩最高的是多少。
当C为'U'的时候,表示这是一条更新操作,要求把ID为A的学生的成绩更改为B。
Output
对于每一次询问操作,在一行里面输出最高成绩。
Sample Input
5 6
1 2 3 4 5
Q 1 5
U 3 6
Q 3 4
Q 4 5
U 2 9
Q 1 5
Sample Output
5
6
5
9*/
#include<bits/stdc++.h>
using namespace std;
const int maxn=2e5+5;  //元素总个数
#define ls l,m,rt<<1
#define rs m+1,r,rt<<1|1
//int Sum[maxn<<2],Add[maxn<<2];//Sum求和,Add为懒惰标记
int MAX[maxn<<2];
int A[maxn],n;//存原数组数据下标[1,n]
//(1)建树:
//PushUp函数更新节点信息 ,这里是求和
void PushUp(int rt)
{
    //Sum[rt]=Sum[rt<<1]+Sum[rt<<1|1];
    //由于这道题不是求和,是求最大值,所以pushup也需要更改
    MAX[rt]=max(MAX[rt<<1],MAX[rt<<1|1]);
}
//Build函数建树
void Build(int l,int r,int rt)  //l,r表示当前节点区间,rt表示当前节点编号
{
    if(l==r)  //若到达叶节点
    {
        MAX[rt]=A[l];//储存数组值

      //  scanf("%d",&MAX[rt]);//这一步是在干啥啊,。,
        return;
    }
    int m=(l+r)>>1;
    //左右递归
    Build(l,m,rt<<1);
    Build(m+1,r,rt<<1|1);
    //更新信息
    PushUp(rt);
}
//(2)点修改:
void Update(int L,int C,int l,int r,int rt) //l,r表示当前节点区间,rt表示当前节点编号
{
    if(l==r) //到叶节点,修改
    {
        MAX[rt]=C;
        return;
    }
    int m=(l+r)>>1;
    //根据条件判断往左子树调用还是往右
    if(L <= m)
        Update(L,C,l,m,rt<<1);
    else
        Update(L,C,m+1,r,rt<<1|1);
    PushUp(rt);//子节点更新了,所以本节点也需要更新信息
}
int query(int L,int R,int l,int r,int rt)//L,R表示操作区间,l,r表示当前节点区间,rt表示当前节点编号
{
    if(L<=l&&r<=R)
    {
        return MAX[rt];
    }
    int m=(l+r)>>1;
    int ret=0;
    if(L<=m)
        ret=max(ret,query(L,R,l,m,rt<<1));
    if(R>m)
        ret=max(ret,query(L,R,m+1,r,rt<<1|1));
    return ret;
}
int main()
{
    int N,M;
    while(scanf("%d%d",&N,&M)!=EOF)
    {

        memset(A,0,sizeof(A));
        memset(MAX,0,sizeof(MAX));
       for(int p=1;p<=N;p++)
        {
            scanf("%d",&A[p]);
        }
        Build(1,N,1);
        char c;
        int a,b;

        for(int t=1;t<=M;t++)
        {
            scanf(" %c%d%d",&c,&a,&b);//当用%c的时候需要注意一下,上面有没有换行符等
          //  cin>>c>>a>>b;用这个也对
            if(c=='Q')
            {

                long long ANS=query(a,b,1,N,1);
                printf("%lld\n",ANS);
            }
            else if(c=='U')
            {

               Update(a,b,1,N,1);
            }
        }
    }
    return 0;
}


线段树求最小逆序数https://vjudge.net/problem/HDU-1394

/*1.hdu1394:给一个0-n-1的排列,这个排列中的逆序数为数对 (ai, aj) 满足 i < j and ai > aj的个数。依次把第一个数放到排列的末尾会得到另外n-1个排列,求这n个排列中的最小的逆序数。
思路:每次把首位移到末尾,逆序数变化是大于a[0]的个数((n-2)-(a[0]-1))-小于a[0]的个数(a[0]),即sum=sum+(n-2)-(a[0]-1)-a[0]。创建线段树的时候就是按下标顺序插入的,所以a[i]产生的逆序数=在a[i]之前插入并且大于a[i]的数,查询a[i]~n-1的Sum即可。然后更新a[i]所在的区间表示这个数已经出现过。
*/
/*the next line contains a permutation of the n integers from 0 to n-1. */
//该序列最大值为n-1,最小值为0
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn=50005;
int Sum[maxn<<2],Add[maxn<<2];//Sum求和,Add为懒惰标记
int A[maxn],n;//存原数组数据下标[1,n]
void PushUp(int rt)
{
    Sum[rt]=Sum[rt<<1]+Sum[rt<<1|1];
}
void Build(int l,int r,int rt)  //l,r表示当前节点区间,rt表示当前节点编号
{
    if(l==r)  //若到达叶节点
    {
        Sum[rt]=0;//储存数组值
        return;
    }
    int m=(l+r)>>1;
    //左右递归
    Build(l,m,rt<<1);
    Build(m+1,r,rt<<1|1);
    //更新信息
    PushUp(rt);
}
void Update(int L,int l,int r,int rt) //l,r表示当前节点区间,rt表示当前节点编号
{
    if(l==r) //到叶节点,修改
    {
        Sum[rt]=1;//代表已经插入
        return;
    }
    int m=(l+r)>>1;
    //根据条件判断往左子树调用还是往右
    if(L <= m) Update(L,l,m,rt<<1);
    else       Update(L,m+1,r,rt<<1|1);
    PushUp(rt);//子节点更新了,所以本节点也需要更新信息
}
void PushDown(int rt,int ln,int rn)
{
    //ln,rn为左子树,右子树的数字数量。
    if(Add[rt])
    {
        //下推标记
        Add[rt<<1]+=Add[rt];
        Add[rt<<1|1]+=Add[rt];
        //修改子节点的Sum使之与对应的Add相对应
        Sum[rt<<1]+=Add[rt]*ln;
        Sum[rt<<1|1]+=Add[rt]*rn;
        //清除本节点标记
        Add[rt]=0;
    }
}
int Query(int L,int R,int l,int r,int rt) //L,R表示操作区间,l,r表示当前节点区间,rt表示当前节点编号
{
    if(L <= l && r <= R)
    {
        //在区间内,直接返回
        return Sum[rt];
    }
    int m=(l+r)>>1;
    //下推标记,否则Sum可能不正确
    PushDown(rt,m-l+1,r-m);
    //累计答案
    int ANS=0;
    if(L <= m) ANS+=Query(L,R,l,m,rt<<1);
    if(R >  m) ANS+=Query(L,R,m+1,r,rt<<1|1);
    return ANS;
}
int main()
{
    int t,k=0;
    int n;
    while(scanf("%d",&n)!=EOF)
    {
        memset(Add,0,sizeof(Add));
        memset(Sum,0,sizeof(Sum));
        Build(0,n-1,1);
        int sum=0;
        for(int i=0; i<n; i++)
        {

           scanf("%d",&A[i]);
            //第一个n-1参数代表最大值,A[i]当前数,因为逆序数是看在当前数位置前的数比它大的有多少个
            //后面三个参数是不动的
            sum+=Query(A[i],n-1,0,n-1,1);
            Update(A[i],0,n-1,1);//把当前节点赋为1,代表已经插入
        }
        int mi=sum;
        for(int i=0;i<n-1;i++)
        {
            sum=sum+n-A[i]*2-1;
            mi=min(mi,sum);
        }
         printf("%d\n",mi);
    }
    return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值