#include<bits/stdc++.h> #define int long long using namespace std; const int N = 1e5+5; int n,f[850],p[N],t[N]; int solve(int now){ int ans = 0; for(int i=1; i<n; i++){ int tmp = p[i] - now % p[i]; if(tmp == p[i]) tmp = 0; now = (now + tmp + t[i])%840; ans = ans + tmp + t[i]; } return ans; } signed main(){ // freopen("in.txt","r",stdin); // freopen("out.txt","w",stdout); int x,y;scanf("%lld%lld%lld",&n,&x,&y); for(int i=1; i<n; i++) scanf("%lld%lld",&p[i],&t[i]); for(int i=0; i<=839; i++) f[i] = solve(i); int q;scanf("%lld",&q); while(q--){ int k;scanf("%lld",&k); printf("%lld\n",f[(k+x)%840] + x + y + k); } return 0; }
代码实现细节的话就是考虑我们使用 set 只维护有用的状态(就是代码里记录的 cur),也就是最短路长度等于枚举值的点,那么考虑这些有效的状态可以转移到哪些点呢。
这些有效的状态可以转移到的点就是之前没有被作为有用状态的点(就是代码里记录的 lst),因为如果一个点被当作有效状态了那么就意味着它的最短路长度一定不会低于我们当前枚举的值,那么再用当前枚举的值去更新这些点更新出来的就一定不是最短路。
然后最后更新 cur 和 lst 数组的时候,因为边操作边遍历在 set 上是很困难的,所以考虑先用 tmp 数组记录下 lst,因为我们可能转移到的点只有 lst 里的点,然后将 lst 与 cur 清空,之后直接遍历 tmp 然后对于一个点 i 我们记录了一个 toti 代表有多少个 cur 内的点通过 m 条给定边转移到 i,显然如果 toti=res2,也就是所有转移到 i 的点都是通过给定的 m 条边转移过去的,也就是没有一个点是真实转移到它的,也就是其依然没有被更新,应放到 lst;反之则应放到 cur。
代码里还有的一点细节就是我们的 E[u] 记录的就是与 u 相连的 m 条边的另一个端点,这里要求另一个端点必须没有被更新到过,因为如果被更新到了那么显然这两者之间的转移就没有任何意义。
#include<bits/stdc++.h> #define int long long using namespace std; const int N = 2e5+5; const int MOD = 998244353; set<int> E[N],cur,lst,tmp; int cnt[N],a[N],tot[N]; signed main(){ int n,m;scanf("%lld%lld",&n,&m); for(int i=1; i<=m; i++){ int u,v;scanf("%lld%lld",&u,&v); E[u].insert(v); E[v].insert(u); } cnt[n] = 1; cur.insert(n); for(int i=1; i<n; i++) lst.insert(i); while(!cur.empty()){ int res = 0,res2 = 0; for(auto now : cur) res = (res + cnt[now])%MOD,res2++; //因为每个点 now 转移到以后的点的贡献都是 cnt[now],所以 res 记录的就是转移造成的总贡献,当前是不考虑 m 条边影响下的 //res2 就是为了方便判断一个点是否被转移到,记录的总共有多少个点可以转移到以后的点 for(auto to : lst) cnt[to] = (cnt[to] + res)%MOD,tot[to] = 0; //这里就是不考虑 m 条边情况下的直接转移 for(auto now : cur){ //枚举每一条给定边,然后将通过这一条边的转移造成的贡献删掉 for(auto to : E[now]){ if(lst.find(to) != lst.end()) //这个 if 是为了防止 to 也在 cur 中直接减就会有影响 cnt[to] = (cnt[to] - cnt[now] + MOD)%MOD,tot[to]++; } } for(auto now : cur){ //因为此时 cur 内的点就都被更新到了,所以就相当于要将 cur 这些点删除 int tot = 0; for(auto to : E[now]) a[++tot] = to; for(int i=1; i<=tot; i++){ //这里就是为了 E 内只维护有用的点 E[a[i]].erase(now); E[now].erase(a[i]); } } swap(tmp,lst); //这里就是更新下一轮的 cur 和 lst,这里本质就是进行了一个枚举的最短路长度加 1,只不过不需要记录长度的值 cur.clear(),lst.clear(); for(auto i : tmp){ if(tot[i] != res2){ cur.insert(i); if(i == 1){ printf("%lld\n",cnt[1]); return 0; } } else lst.insert(i); } tmp.clear(); } printf("-1\n"); return 0; }