2017.10.24 求逆序對+表達式計算+貪心+Bfs+最短路、exgcd

*PS:爲什麼我t5死活都調不出來呢(눈‸눈)

T1:原創,參考noip火柴排隊。
T2: codevs 2178 表達式運算Cuties (我自己找的)
T3:Codeforce867E(865D) Buy Low Sell High
T4: USACO 2015 DEC Gold  不明鏈接1   不明鏈接2
( 該網站不提供在線測評,僅提供數據下載。考試使用原數據。)
T5:原創。


這裏寫圖片描述
這裏寫圖片描述


這裏寫圖片描述


T1 原創 火柴排隊的改編版

一說到火柴排隊,應該就會做了。(好巧 這個題我前些天才看過qwq)。

解法:
對於B中的每個字符都統計下他在A中出現的位置,存入一個數組裏,這個位置就是它最後要移到的位置。最後對這個數組求一個逆序對,逆序對的數量就是最少需移動的步數。
不能的情況就是B和A的字符種類或數量不同,輸入時處理。

代碼

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;

int N,ans,cnt,Cnt;
int a[110],used[1000],b[110];
char c[110];
struct maple{
    int pos;
    char a;
}A[110],B[110];

bool cmp(maple c,maple d)
{
    return c.a<d.a;
}

void hebing(int l,int mid,int r)
{
    int i=l,j=mid+1,k=l-1;
    while(i<=mid&&j<=r)
    {
        if(a[i]<=a[j]) b[++k]=a[i],++i;
        else b[++k]=a[j],ans+=mid-i+1,++j;
    }
    while(i<=mid) b[++k]=a[i],++i;
    while(j<=r)  b[++k]=a[j],++j;
    for(int i=l;i<=r;++i)
      a[i]=b[i];
}
void Mergesort(int l,int r)
{
    if(l==r) return ;
    int mid=(l+r)>>1;
    Mergesort(l,mid);
    Mergesort(mid+1,r);
    hebing(l,mid,r);
} 
int main()
{
    freopen("order.in","r",stdin);
    freopen("order.out","w",stdout);
    scanf("%d",&N);
    scanf("%s",c);
    for(int i=0;i<N;++i)
    {
        A[i].a=c[i],A[i].pos=i;
        if(!used[c[i]]) ++cnt;
        ++used[c[i]];
    }
    scanf("%s",c);
    for(int j=0;j<N;++j)
    {
        B[j].a=c[j],B[j].pos=j;
        --used[c[j]];
        if(!used[c[j]]) ++Cnt;
        if(used[c[j]]<0) Cnt=-110;
    }
    if(cnt==Cnt)
    {
        sort(A,A+N,cmp);
        sort(B,B+N,cmp);
        for(int i=0;i<N;++i)
            a[B[i].pos]=A[i].pos;
        Mergesort(0,N-1);
        cout<<ans;
    }
    else cout<<-1;
    return 0;
}

這裏寫圖片描述


T2 簡單的棧應用

注意處理負數的情況,判斷棧空的函數一定要放在最前面。

代碼

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<stack>
using namespace std;

int k[100],f=1;
char a[210];
stack<long long> num;
stack<char> opt;

void Done()
{
    char x=opt.top();
    opt.pop();
    long long a=num.top();num.pop();
    long long b=num.top();num.pop();
    long long c=0;
    if(x=='+') c=a+b;
    if(x=='-') c=b-a;
    if(x=='*') c=b*a;
    if(x=='/') c=b/a;
    if(x=='^') 
    {
      c=1;
      while(a)
      {
          c*=b;
          --a;
      }
    }
    num.push(c);
}
int main()
{
    freopen("formula.in","r",stdin);
    freopen("formula.out","w",stdout);
    scanf("%s",a);
    k['+']=k['-']=1;
    k['*']=k['/']=2;
    k['^']=3;
    k['(']=4;
    num.push(0);
    long long l=strlen(a),Num=0;
    a[l]=')';
    for(int i=0;i<=l;++i)
    {
        if(a[i]>='0'&&a[i]<='9') Num=Num*10+(a[i]-'0'),Num*=f,f=1;
        else{
               if(Num!=0) num.push(Num); 
               Num=0;
               if(a[i]=='-'&&a[i-1]=='(') 
               {
                  f=-1;
                  continue;
               }
               if(a[i]==')')
               {
                   while(!opt.empty()&&opt.top()!='(')   Done();
                   if(!opt.empty()) opt.pop();
               }
               else if(opt.empty()||opt.top()=='('||k[a[i]]>k[opt.top()])      opt.push(a[i]);
               else 
               {
                   while(!opt.empty()&&opt.top()!='('&&k[a[i]]<=k[opt.top()]) Done();
                   opt.push(a[i]);
               }
        }
    }
    while(!opt.empty()&&opt.top()!='(') Done();
    cout<<num.top();
    return 0; 
}

這裏寫圖片描述
這裏寫圖片描述


T3 看似DP的貪心

是不是很像之前的一道有N天每天可買可賣可不作處理的dp問題??

但是那個題有個限制就是每天手裏最多持有一份股票,而這個題則是多份的。

因爲這個題是t3而之前又做過類似的題,所以我馬上就在想dp的路上一去不復返了QAQ。。。

最後確實打出來了,測的時候卻顯示MLE!!!因爲long long的數組開的太大!!! 好吧。。。最後知道long long 在128MB下也就能開個1600萬+。。。

定義 f[i][j]表示到第i天手裏有j份股票所賺到的最多的錢數

可知

   f[i][j]=max(f[i][j],f[i-1][j]);         // 不作處理
   f[i][j]=max(f[i][j],f[i-1][j+1]+P[i]);  // 賣出
   f[i][j]=max(f[i][j],f[i-1][j-1]-P[i]);  //買入

改完後可以過5個點

DP代碼

#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<cstdio>
using namespace std;
const int Cn=300000+10;

int N;
long long P[Cn],f[4000][4000];

int main()
{
    freopen("resell.in","r",stdin);
    freopen("resell.out","w",stdout);
    scanf("%d",&N);
    for(int i=1;i<=N;++i) 
       scanf("%lld",&P[i]);
    for(int i=1;i<=N;++i)
       for(int j=0;j<=N;++j)
       {
          f[i][j]=-1e9+7;
          if(j<=i) 
          {
               if(i!=1)
               {
                 f[i][j]=max(f[i][j],f[i-1][j]);
                 f[i][j]=max(f[i][j],f[i-1][j+1]+P[i]);
                 if(j-1>=0) f[i][j]=max(f[i][j],f[i-1][j-1]-P[i]);
               } 
               else f[1][0]=0,f[1][1]=-P[1];
          }
       }
    cout<<f[N][0];
    return 0;
}

emmmm。。。如果用滾動數組優化的話可以過6個點,就是另開一個數組先記錄一下f[i-1]的值用來更新。。。這裏就不給出代碼了。

正解 ·貪心

來自loi.Sherlock的題解總結(就是講題時我做的筆記啊qwq,略長)

emmm...要賺的錢最多,我們肯定是低價買入,高價買出。。

大體上想想:可以維護一個小根堆,每過一天,用當天的價格和堆頂比較,如果價格大於堆頂,ans就加上*當天價格-堆頂價格*,彈出堆頂;如果小於等於,就加入堆。

但是這個做法有一個bug,舉個栗子:

2 5 10 

按照我們現在的貪心做法,答案應該是 5-2=3 (每天只能操作一次),但是正確答案應該是 10-2=8

這就很尷尬了。。。(劃掉)

這時我們發現 10-2=(10-5)+(5-2) ,這是不是代表我可以利用一下5呢??

對於 a<b<c 的情況,我要利用中介b ,就把b放一個進去(之前是b>a時,只統計ans,b並沒有被放入)用來做中介。

但是這樣還有一個bug 

如 2 5 10 7   輸出 10-5+5-2=8 ,但正確答案是 10-2+7-5=10

我們發現之前的5實際上並沒有被用到,反而被彈出了。

解決的方法就是一開始就放上兩個5,一個做中介,一個用來更新答案。

所以對之前貪心做法的改進就是當我遇到一個比堆頂大的元素時,更新ans,彈出堆頂,同時放入兩個當前價格,對答案不會有影響。

代碼

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
using namespace std;

int N;
long long ans=0;
int P[300010];

priority_queue<int,vector<int>,greater<int> >q;

int main()
{
    freopen("resell.in","r",stdin);
    freopen("resell.out","w",stdout);
    scanf("%d",&N);
    for(int i=1;i<=N;++i)
    {
        scanf("%d",&P[i]);
        if(!q.empty()&&P[i]>q.top())
        {
            ans+=P[i]-q.top();
            q.pop();
            q.push(P[i]);
        }
        q.push(P[i]);
    }
    printf("%lld",ans);
    return 0;
}

這裏寫圖片描述
這裏寫圖片描述


T4 無比麻煩的BFS

注意特判啊!!我做的時候WA,TLE,MLE真是夠了!

注意紫色的格子上的移動方向是與之前進入的方向有關的,並不是可以隨便走的!! 所以要另開一維記錄前進的方向,但是並不是每個格子的前進方向都要記錄,只需記錄紫色格子的,不然會TLE。。

代碼

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
using namespace std;
const int mxn=1000+5;

int N,M,f;
int x[4]={0,1,0,-1},  //  0--> 右 1--> 下 2--> 左 3-->上 
    y[4]={1,0,-1,0};  
int A[mxn][mxn];
int used[mxn][mxn][2][5];
struct maple{
    int x,y,z,face,d;
};
queue<maple>q;  // 手打隊列會mle 

bool can(int k,maple a,int K)
{
    int X=a.x+x[k],Y=a.y+y[k];
    if(X<=0||X>N||Y<=0||Y>M||used[X][Y][a.z][K]) return false;
    if(A[X][Y]==0) return false;    
    if(A[X][Y]==3&&!a.z) return false;
    return true;
}
void Bfs()
{
    q.push((maple){1,1,0,0,0});
    used[1][1][0][4]=1;
    while(!q.empty())
    {
        maple a=q.front();
        q.pop();
        if(a.x==N&&a.y==M)  
        {
             f=1;
             printf("%d",a.d);
             return ;
        }
        if(A[a.x][a.y]==4)
        {
           int face=a.face;
           int X=a.x+x[a.face],Y=a.y+y[a.face];
           if(A[X][Y]!=4) face=4;  // 記錄每個格子的方向會T 
           if(used[X][Y][a.z][face]) continue;
           if(can(a.face,a,face))
           {
               int xx=a.x+x[a.face],yy=a.y+y[a.face],z=a.z,d=a.d+1;
               if(A[xx][yy]==2) z=1,used[xx][yy][0][face]=1;   // 不加會T 1
               q.push((maple){xx,yy,z,face,d});
               used[xx][yy][z][face]=1;
               continue;
           }
        }
        for(int i=0;i<4;++i)
        {
            int xx=a.x+x[i],yy=a.y+y[i],face=i;
            if(A[xx][yy]!=4) face=4; 
            if(can(i,a,face))
            {
                int z=a.z,d=a.d+1;
                if(A[xx][yy]==4) z=0;
                if(A[xx][yy]==2) z=1,used[xx][yy][0][face]=1;  //不加會T 2
                q.push((maple){xx,yy,z,face,d});
                used[xx][yy][z][face]=1;
            }
        }
    }
}
int main()
{
    freopen("cowboy.in","r",stdin);
    freopen("cowboy.out","w",stdout);
    scanf("%d%d",&N,&M);
    for(int i=1;i<=N;++i)
       for(int j=1;j<=M;++j)
          scanf("%d",&A[i][j]);
    Bfs();
    if(!f) cout<<-1;
    return 0; 
}

//*PS:註釋的都是血的教訓。。。QAQ

這裏寫圖片描述
這裏寫圖片描述
這裏寫圖片描述


T5 十分明顯的exgcd??

再次%%%原創題dalao @loi.MurasameKatana

由於這個題我還是沒能調出來,所以我打算十分不要臉的打算粘上題解和std_(:з」∠)_ 。。。。ε=ε=ε=┌(; ・_・)┘跑

而對於本題,首先我們將起點到所有城鎮的距離(最短路)排序,每次選取存在妖刀且路程之和爲當前最小的兩個城鎮中的妖刀,取其攻擊力爲a b,結界初始防禦值-臨界防禦值爲c,那麼兩把妖刀各自的使用次數爲方程的解x,y

那麼我們首先通過exgcd()求出一組解滿足ax + by = gcd(a,b)

注:exgcd()求出的特解爲 |x| + |y| 最小的一組解

然後得到滿足ax + by = c的解,再推導其他的解找到:

兩個使用次數大於0,使用方案不會使任意一把妖刀折斷,且消耗的磨損值之和最小的解。

這便是T5朧村正的算法過程

然後就是注意細節處理

by  Loi_MurasameKatana

(侵刪)

Std

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#define RI register int
using namespace std;
typedef long long ll;
const int INF =1000000007;
ll n,m,h,p,ru,rv,rw,tot,x,y,xs,ys,ans,anz,A,Z,minx,f;
ll dis[100010],first[200010],nxt[200010],c[100010],r[100010],k[100010],t[100010],num[100010];
bool flag;
bool inq[100010],unb[100010];
struct edge
{
    ll u,v,w;
}l[400010];
queue<int>q;
inline bool cmp(ll a,ll b)
{
    if(dis[a]!=dis[b])
    return dis[a]<dis[b];
    else return a<b;
}
inline void build(ll f,ll t,ll c)
{
    l[++tot]=(edge){f,t,c};
    nxt[tot]=first[f];
    first[f]=tot;
}
inline bool check_1(ll a,ll b,ll c)//分情況討論邊界條件 
{
    if(a>0&&b>0)
    {
        if(xs<=0||ys>(k[num[c]]/r[num[c]]))
        return 0;
    }
    else if(a>0&&b<0)
    {
        if(xs>(k[num[c-1]]/r[num[c-1]])||ys>(k[num[c]]/r[num[c]]))
        return 0;
    }
    else if(a<0&&b>0)
    {
        if(xs<=0||ys<=0)
        return 0;
    }
    else if(a<0&&b<0)
    {
        if(xs>(k[num[c]]/r[num[c]])||ys<=0)
        return 0;
    }
    return 1;
}
inline bool check_2(ll a,ll b,ll c)
{
    if(a>0&&b>0)
    {
        if(xs>(k[num[c-1]]/r[num[c-1]])||ys<=0)
        return 0;
    }
    else if(a>0&&b<0)
    {
        if(xs<=0||ys<=0)
        return 0;
    }
    else if(a<0&&b>0)
    {
        if(xs>(k[num[c-1]]/r[num[c-1]])||ys>(k[num[c]]/r[num[c]]))
        return 0;
    }
    else if(a<0&&b<0)
    {
        if(xs<=0||ys>(k[num[c]]/r[num[c]]))
        return 0;
    }
    return 1;
}
inline void SPFA()
{
    for(RI i=1;i<=n;i++)
    dis[i]=INF;
    dis[1]=0;
    q.push(1);
    inq[1]=1;
    while(!q.empty())
    {
        int k=q.front();
        q.pop();
        inq[k]=0;
        for(RI i=first[k];i!=-1;i=nxt[i])
        {
            ll x=l[i].v;
            if(dis[x]>dis[k]+l[i].w)
            {
                dis[x]=dis[k]+l[i].w;
                if(!inq[x])
                {
                    q.push(x);
                    inq[x]=1;
                }
            }
        }
    }
}
inline ll exgcd(ll a,ll b,ll &x,ll &y)
{
    if(b==0)
    {
        x=1;
        y=0;
        return a;
    }
    ll d=exgcd(b,a%b,x,y);
    ll t=x;
    x=y;
    y=t-a/b*y;
    return d;
}
int main()
{
    freopen("blade.in","r",stdin);
    freopen("blade.out","w",stdout);
    memset(first,-1,sizeof(first));
    scanf("%lld%lld%lld%lld",&n,&m,&h,&p);
    for(RI i=1;i<=m;i++)
    {
        scanf("%lld%lld%lld",&ru,&rv,&rw);
        build(ru,rv,rw);
        build(rv,ru,rw);
    }
    for(RI i=1;i<=n;i++)
    {
        scanf("%lld%lld%lld%lld",&c[i],&r[i],&k[i],&t[i]);
        if(t[i]==1)
        c[i]*=-1;
        if(!c[i]&&!r[i]&&!k[i]&&!t[i])
        unb[i]=1;
    }
    SPFA();
    for(RI i=1;i<=n;i++)
    if(unb[i]==1)
    dis[i]=INF;
    for(RI i=1;i<=n;i++)
    num[i]=i;
    sort(num+1,num+n+1,cmp); // 給城鎮排序 
    minx=1e9+7;
    for(RI i=2;i<=n;i+=2)
    {
        if(unb[num[i-1]]||unb[num[i]]) // 若搜遍周邊城鎮也沒有找到 
        break;
        ll d=exgcd(c[num[i-1]],c[num[i]],x,y);//ax+by=gcd(a,b) 
        if(!d||(h-p)%d!=0)  //%0RE 若無解 
        continue;
        ll s=(h-p)/d;//asx+bsy=c 
        x*=s;
        y*=s;
        if(x<y)
        {
            swap(x,y);
            swap(c[num[i-1]],c[num[i]]);
            swap(r[num[i-1]],r[num[i]]);
            swap(k[num[i-1]],k[num[i]]);
        }
        int li=x;
        int lr=y;
        for(RI j=0;;j++)//j>=0 
        {
            xs=x-(c[num[i]]/d)*j;//xs=x-b/d*j
            ys=y+(c[num[i-1]]/d)*j;//ys=y+a/d*j
            if(!check_1(c[num[i-1]]/d,c[num[i]]/d,i))
            break;
            if(xs>0&&ys>0&&xs<=(k[num[i-1]]/r[num[i-1]])&&ys<=(k[num[i]]/r[num[i]]))//合法情況,使用次數大於0且未達次數上限 
            {
                if(((xs*r[num[i-1]]+ys*r[num[i]])<minx)||(A!=0&&Z!=0&&(xs*r[num[i-1]]+ys*r[num[i]]==minx)&&(abs(xs*r[num[i-1]]-ys*r[num[i]])<abs(A*r[num[i-1]]-Z*r[num[i]]))))//取磨損值消耗之和最小的方案 
                {//若存在多解取磨損值相差最小的 
                    minx=(xs*r[num[i-1]]+ys*r[num[i]]);
                    A=xs;
                    Z=ys;
                    flag=1;//存在合法解
                }
            }
        }
        x=li;
        y=lr;
        for(RI j=-1;;j--)//j<0
        {
            xs=x-(c[num[i]]/d)*j;
            ys=y+(c[num[i-1]]/d)*j;//x爲大正數,y爲大負數的情況不會導致非法退出
            if(!check_2(c[num[i-1]]/d,c[num[i]]/d,i))
            break;
            if(xs>0&&ys>0&&xs<=(k[num[i-1]]/r[num[i-1]])&&ys<=(k[num[i]]/r[num[i]]))
            {
                if(((xs*r[num[i-1]]+ys*r[num[i]])<minx)||(A!=0&&Z!=0&&(xs*r[num[i-1]]+ys*r[num[i]]==minx)&&(abs(xs*r[num[i-1]]-ys*r[num[i]])<abs(A*r[num[i-1]]-Z*r[num[i]]))))
                {
                    minx=(xs*r[num[i-1]]+ys*r[num[i]]);
                    A=xs;
                    Z=ys;
                    flag=1;
                }
            }
        }
        if(flag)
        {
            ans=min(A*r[num[i-1]],Z*r[num[i]]);//統計 
            anz=max(A*r[num[i-1]],Z*r[num[i]]);
            break;
        }
    }
    if(flag)
    {
        printf("%lld %lld\n",ans,anz);
    }
    else printf("Game over\n");
    return 0;
}

The end.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章