1. 程式人生 > 遊戲資訊 >原神:2.7版本攻略作者失業?角色自帶聖遺物、天賦推薦,成就可篩選

原神:2.7版本攻略作者失業?角色自帶聖遺物、天賦推薦,成就可篩選

同餘最短路

什麼神仙演算法

這類問題的關鍵在於建模,頭大了,主要總結幾個題的思路技巧吧。

(標題有 Link)

P3403 跳樓機

明顯的樓層數只能在 \([1,k]\) 之間,因為可以回到第一層,那麼問題轉化一下:

滿足 \(ax+by+cz \equiv i \pmod k\)\(i\) 有幾個。

對於每一個 \(i\) ,我們可以把它表示為 \(i=xn+m\)\(m\) 為餘數,可以用 \(y,z\) 去湊出 \(m\) 這個餘數,任何可以在 \([0,x-1]\) 範圍內統計餘數就可以得出樓層數,設 \(dis[i]\) 表示用若干個 \(y,z\) 能組成的餘數(或者說能到達的位置,餘數 \(\in (0,x-1]\)

),對於餘數 \(m\) ,通過加一個 \(y\) 能到達餘數 \(j\) ,則一定滿足 \((m+y) \bmod x=j\),可以這麼說,餘數從 \(i\)\(j\) 需要走的路徑長度為 \(y\)

所以這樣連邊:

\[Add\_edge(m,(m+y)\bmod a,y) \] \[Add\_edge(m,(m+z)\bmod a,z) \]

跑一遍最短路,得到了 \([0,x)\) 範圍內的能組成的餘數 \(dis[i],i\in[0,x)\)

因為 \(x\) 可能比 \(k\) 大,所以只選取餘數比 \(k\) 小的部分,看 \(k-dis[i]\) 中有幾個 \(a\)

統計貢獻即可,\(+1\) 是為了加上 \(dis\) 本身這個位置。

\[Ans=\displaystyle \sum_{m=0}^{a-1}(\frac{h-dis_m}{a}+1) \]
/*
Knowledge : Rubbish Algorithm
Work by :Gym_nastics
Time : O(AC)
*/
#include<bits/stdc++.h>
#define int long long
using namespace std;

const int INF=0x3f3f3f3f;
const int N=1e6+6;

int read() {
    int x=0,f=0;char ch=getchar();
    for(;!isdigit(ch);ch=getchar()) f|=(ch=='-');
    for(;isdigit(ch);ch=getchar()) x=(x<<1)+(x<<3)+(ch&15);
    return f?-x:x;
}

void print(int x) {
    if(x<0) putchar('-'),x=-x;
    if(x>9) print(x/10);
    putchar(x%10+48);
}

bool vis[N];
int dis[N],head[N],cnt;struct node{int v,w,nxt;}e[N];
void Add_edge(int u,int v,int w){e[++cnt]=(node){v,w,head[u]};head[u]=cnt;}

int h,a,b,c;

void SPFA(){
    queue<int>q;memset(dis,INF,sizeof dis);
    dis[1]=1;q.push(1);vis[1]=true;
    while(!q.empty()){
        int u=q.front();q.pop();vis[u]=false;
        for(int i=head[u];i;i=e[i].nxt){int v=e[i].v;
            if(dis[v]>dis[u]+e[i].w){
                dis[v]=dis[u]+e[i].w;
                if(!vis[v]) q.push(v),vis[v]=true;
            } 
        }
    }
}

signed main() {
   h=read();a=read();b=read();c=read();if(a==1 or b==1 or c==1) return print(h),0;
   for(int i=0;i<a;i++)Add_edge(i,(i+b)%a,b),Add_edge(i,(i+c)%a,c);
   SPFA();int Ans=0; 
   for(int i=0;i<a;i++)if(h>=dis[i]) Ans+=(((h-dis[i])/a)+1);
   return print(Ans),0;
}

AT3621 [ARC084B] Small Multiple

是個人都知道暴力不可過……

需要知道一點的是:一個數通過不停 \(+1\)\(\times 10\) 能構成任何數,並且 \(+1\) 對和的影響是 \(0\)\(\times 10\) 的影響是 \(0\),設 \(dis[i]\) 表示 \(x \bmod k=i\) 的最小數字和,則 \(dis[0]\) 表示整除的情況。那就比較簡單了。

\[Add\_edge(i,i+1,1) \] \[Add\_edge(i,(i\times 10)\bmod k,0) \]

注意 \(1\) 的數字和是 \(1\),跑一遍最短路。

/*
Knowledge : Rubbish Algorithm
Work by :Gym_nastics
Time : O(AC)
*/
#include<bits/stdc++.h>
using namespace std;

const int INF=0x3f3f3f3f;
const int N=1e6+6;

int read() {
    int x=0,f=0;char ch=getchar();
    for(;!isdigit(ch);ch=getchar()) f|=(ch=='-');
    for(;isdigit(ch);ch=getchar()) x=(x<<1)+(x<<3)+(ch&15);
    return f?-x:x;
}

void print(int x) {
    if(x<0) putchar('-'),x=-x;
    if(x>9) print(x/10);
    putchar(x%10+48);
}

int head[N],cnt;struct node{int v,w,nxt;}e[N];
void Add_edge(int u,int v,int w){e[++cnt]=(node){v,w,head[u]};head[u]=cnt;}
int k;int dis[N];bool vis[N];

void SPFA(){
    memset(dis,INF,sizeof dis);
    dis[1]=1;vis[1]=1;queue<int>q;q.push(1);
    while(!q.empty()){
        int u=q.front();q.pop();vis[u]=0;
        for(int i=head[u];i;i=e[i].nxt){
            int v=e[i].v;
            if(dis[v]>dis[u]+e[i].w){
                dis[v]=dis[u]+e[i].w;
                if(!vis[v]) vis[v]=1,q.push(v);
            }
        }
    }
}

signed main() {
   k=read();
   for(int i=1;i<k;i++) 
   Add_edge(i,(i+1)%k,1),Add_edge(i,(i*10)%k,0);
   SPFA();print(dis[0]);                                 
   return 0;
}