8.10考試總結(NOIP模擬35)[玩遊戲·排列·最短路·矩形]
T1 玩遊戲
解題思路
可以把序列從 k 位置掰成兩個序列。
問題就變成了兩個序列從開頭走向末尾是否可以保證字首和之和一直不大於 0 。
並且可以移動到兩個序列的末尾,問題就變成處理字首和。
然後在每一個序列裡維護一個 next 值,表示可以跳到的較小值。
這裡需要正反掃一遍,畢竟只掃一邊的話會有最小值一邊的 next 無法更新。
然後就是對於兩個序列分別從兩邊掃一邊。
看看是否可以跳到對應的 next 位置。
code
#include<bits/stdc++.h> #define int long long #define ull unsigned long long #define f() cout<<"Pass"<<endl using namespace std; inline int read() { int x=0,f=1; char ch=getchar(); while(ch>'9'||ch<'0') { if(ch=='-') f=-1; ch=getchar(); } while(ch>='0'&&ch<='9') { x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); } return x*f; } const int N=1e5+10,INF=1e18; int T,n,k,l,r,sum,s[N],nxt1[N],nxt2[N]; int a[N],b[N],cnt1,cnt2,pre1[N],pre2[N]; void solve() { memset(nxt1,0,sizeof(nxt1)); memset(nxt2,0,sizeof(nxt2)); n=read(); k=read(); cnt1=cnt2=0; for(int i=1;i<=n;i++) s[i]=read(); a[++cnt1]=0; b[++cnt2]=0; for(int i=k+1;i<=n;i++) a[++cnt1]=s[i]; for(int i=k;i>1;i--) b[++cnt2]=s[i]; for(int i=1;i<=cnt1;i++) pre1[i]=pre1[i-1]+a[i]; for(int i=1;i<=cnt2;i++) pre2[i]=pre2[i-1]+b[i]; int pos,minn; minn=pre1[1]; pos=1; for(int i=2;i<=cnt1;i++) if(minn>=pre1[i]) { nxt1[pos]=i; pos=i; minn=pre1[i]; } minn=pre1[cnt1]; pos=cnt1; for(int i=cnt1-1;i>=1;i--) if(minn>pre1[i]) { nxt1[pos]=i; pos=i; minn=pre1[i]; } minn=pre2[1]; pos=1; for(int i=2;i<=cnt2;i++) if(minn>=pre2[i]) { nxt2[pos]=i; pos=i; minn=pre2[i]; } minn=pre2[cnt2]; pos=cnt2; for(int i=cnt2-1;i>=1;i--) if(minn>pre2[i]) { nxt2[pos]=i; pos=i; minn=pre2[i]; } if(pre1[cnt1]+pre2[cnt2]>0) { printf("No\n"); return ; } int pos1=1,pos2=1; bool flag=true; while(nxt1[pos1]||nxt2[pos2]) { flag=true; for(int i=pos1+1;i<=nxt1[pos1];i++) if(pre1[i]+pre2[pos2]>0) { flag=false; break; } if(!nxt1[pos1]) flag=false; if(flag){pos1=nxt1[pos1];continue;} flag=true; for(int i=pos2+1;i<=nxt2[pos2];i++) if(pre1[pos1]+pre2[i]>0) { flag=false; break; } if(!nxt2[pos2]) flag=false; if(!flag) break; pos2=nxt2[pos2]; flag=true; } if(!flag) { printf("No\n"); return ; } pos1=cnt1;pos2=cnt2; while(nxt1[pos1]||nxt2[pos2]) { flag=true; for(int i=pos1-1;i>=nxt1[pos1];i--) if(pre1[i]+pre2[pos2]>0) { flag=false; break; } if(!nxt1[pos1]) flag=false; if(flag){pos1=nxt1[pos1];continue;} flag=true; for(int i=pos2-1;i>=nxt2[pos2];i--) if(pre1[pos1]+pre2[i]>0) { flag=false; break; } if(!nxt2[pos2]) flag=false; if(!flag) break; pos2=nxt2[pos2]; flag=true; } if(!flag) { printf("No\n"); return ; } printf("Yes\n"); } signed main() { T=read(); while(T--) solve(); return 0; }
排列
解題思路
動態規劃。
DP 陣列 \(f_{i,j,0/1,0/1}\) 表示區間長度為 i 操作 j 次正好可以只剩下一個的方案數。
0 或者 1 分別表示左邊或者右邊是否有更大的或者邊界。
然後就可以通過之前的狀態和組合數進行轉移。
分別列舉現在區間的長度,操作次數,子區間長度,以及子區間操作次數進行轉移。
轉移的時候就好像在兩個子區間中間插進去一個更大的數,其實是和挨著邊界差不多的。
但是這樣顯然會 TLE 因此需要字首和優化。
轉移也是差不多的,只不過省掉了一維列舉子區間的操作。
然後直接整階乘進行組合數的計算是會被卡常的,因此需要楊輝三角處理。。
code
#include<bits/stdc++.h> #define int long long #define ull unsigned long long #define f() cout<<"Pass"<<endl using namespace std; inline int read() { int x=0,f=1;char ch=getchar(); while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();} return x*f; } const int N=1e3+10; int n,m,mod,c[N][N],f[N][15][2][2]; void init() { c[0][0]=1; for(int i=1;i<=n;i++) { c[i][0]=c[i][i]=1; for(int j=1;j<i;j++) c[i][j]=(c[i-1][j]+c[i-1][j-1])%mod; } } signed main() { n=read(); m=read(); mod=read(); init(); for(int i=0;i<=m;i++) f[0][i][0][0]=f[0][i][1][0]=f[0][i][0][1]=f[0][i][1][1]=1; for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) for(int k=1;k<=i;k++) { f[i][j][0][0]=(f[i][j][0][0]+f[k-1][j][0][1]*f[i-k][j][1][0]%mod*c[i-1][k-1])%mod; f[i][j][1][0]=(f[i][j][1][0]+f[k-1][j][1][0]*f[i-k][j-1][1][1]%mod*c[i-1][k-1])%mod; f[i][j][0][1]=(f[i][j][0][1]+f[k-1][j][0][1]*f[i-k][j-1][1][1]%mod*c[i-1][k-1])%mod; int tmp1=(f[k-1][j][1][1]-f[k-1][j-1][1][1]+mod)%mod; int tmp2=(f[i-k][j][1][1]-f[i-k][j-1][1][1]+mod)%mod; f[i][j][1][1]=(f[i][j][1][1]+(f[k-1][j][1][1]*f[i-k][j][1][1]%mod-tmp1*tmp2%mod+mod)*c[i-1][k-1])%mod; } printf("%lld",(f[n][m][0][0]-f[n][m-1][0][0]+mod)%mod); return 0; }
最短路
解題思路
考場上是想了一個假做法,先從 1 為源點跑一邊,用 bitset 進行維護。
然後再第一次的 Dij 的基礎上繼承以後再跑一次,更新答案。
然後這個做法是 WA 了兩個點,但是由於測試是 subtask 的,所以就 20pts 了。
Yubai 的思路非常的妙!!
對於每一個有的邊建一條反邊,跑 二維DIJ。
\(dis_{i,j}\) 表示 從 1 節點沿正邊到達 i 點以及沿反邊到 j 點的距離之和。
這樣 \(dis_{n,n}\) 表示的就是從 1 沿正邊到 n 在沿正邊走回來的距離。
同樣用 bitset 維護,因為這個是動態的,因此具有正確性。
二維Dij 的時候注意兩步不可以同時進行要一步一步來。。
通俗來講就是兩個迴圈而不是兩層迴圈。。。
code
20ptsWA兩個點
#include<bits/stdc++.h>
#define int long long
#define ull unsigned long long
#define f() cout<<"Pass"<<endl
using namespace std;
inline int read()
{
int x=0,f=1;
char ch=getchar();
while(ch>'9'||ch<'0')
{
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return x*f;
}
const int N=260,M=N*N,INF=1e18;
int n,m,ans=INF,s[N],dis[N],dis2[N];
int tot=1,head[N],nxt[M],ver[M],edge[M];
bitset<N> bit[N],bit2[N];
bool vis[N],b[M];
priority_queue<pair<int,int> > q;
void add_edge(int x,int y)
{
ver[++tot]=y;
nxt[tot]=head[x];
head[x]=tot;
}
void check(int x)
{
b[x]=true;
for(int i=head[x];i;i=nxt[i])
if(!b[ver[i]]&&vis[ver[i]])
check(ver[i]);
}
bool judge()
{
bool jud1,jud2;
check(1); jud1=b[n];
memset(b,false,sizeof(b));
check(n); jud2=b[1];
memset(b,false,sizeof(b));
return jud1&&jud2;
}
void Dij()
{
memset(dis,0x3f,sizeof(dis));
dis[1]=s[1]; bit[1][1]=true;
q.push(make_pair(-dis[1],1));
while(!q.empty())
{
int x=q.top().second;q.pop();
if(vis[x]) continue;
vis[x]=true;
for(int i=head[x];i;i=nxt[i])
{
int to=ver[i],sum=0;
bitset<N> bi=bit[to]|bit[x];
for(int j=1;j<=n;j++)
if(bi[j])
sum+=s[j];
if(!bi[to])
{
bi[to]=true;
sum+=s[to];
}
if(sum<dis[to])
{
dis[to]=sum;
bit[to]=bi;
q.push(make_pair(-dis[to],to));
}
}
}
}
void Dij2()
{
memset(dis2,0x3f,sizeof(dis2));
dis2[n]=dis[n]; bit2[n]=bit[n];
q.push(make_pair(-dis2[n],n));
while(!q.empty())
{
int x=q.top().second;q.pop();
if(vis[x]) continue;
vis[x]=true;
for(int i=head[x];i;i=nxt[i])
{
int to=ver[i],sum=0;
bitset<N> bi=bit2[to]|bit2[x];
for(int j=1;j<=n;j++)
if(bi[j])
sum+=s[j];
if(!bi[to])
{
bi[to]=true;
sum+=s[to];
}
if(sum<dis2[to])
{
dis2[to]=sum;
bit2[to]=bi;
q.push(make_pair(-dis2[to],to));
}
}
}
}
void dfs(int x,int cnt)
{
if(x==n)
{
if(judge()) ans=min(ans,cnt);
return ;
}
dfs(x+1,cnt);
vis[x]=true;
dfs(x+1,cnt+s[x]);
vis[x]=false;
}
void solve()
{
memset(vis,false,sizeof(vis));
vis[1]=vis[n]=true;
dfs(2,s[1]+s[n]);
printf("%lld",ans);
exit(0);
}
signed main()
{
n=read(); m=read();
for(int i=1;i<=n;i++)
s[i]=read();
for(int i=1,x,y;i<=m;i++)
{
x=read(); y=read();
add_edge(x,y);
}
memset(vis,true,sizeof(vis));
if(!judge())
{
printf("-1");
return 0;
}
memset(vis,false,sizeof(vis));
Dij();
memset(vis,false,sizeof(vis));
Dij2();
printf("%lld",dis2[1]);
return 0;
}
正解
#include<bits/stdc++.h>
#define int long long
#define ull unsigned long long
#define f() cout<<"Pass"<<endl
using namespace std;
inline int read()
{
int x=0,f=1;
char ch=getchar();
while(ch>'9'||ch<'0')
{
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return x*f;
}
const int N=260,M=N*N,INF=0x3f3f3f3f3f3f3f3f;
int n,m,s[N],dis[N][N];
int tot,head[N],nxt[M],ver[M];
int tot2,head2[N],nxt2[M],ver2[M];
bool vis[N][N];
bitset<N> bit[N][N];
struct Node
{
int dat,x,y;
bool friend operator < (Node x,Node y)
{
return x.dat>y.dat;
}
};
priority_queue<Node> q;
void add_edge(int x,int y)
{
ver[++tot]=y;
nxt[tot]=head[x];
head[x]=tot;
}
void add_edge2(int x,int y)
{
ver2[++tot2]=y;
nxt2[tot2]=head2[x];
head2[x]=tot2;
}
void Dij()
{
memset(dis,0x3f,sizeof(dis));
dis[1][1]=s[1];
bit[1][1][1]=true;
q.push((Node){dis[1][1],1,1});
while(!q.empty())
{
int x=q.top().x,y=q.top().y;q.pop();
if(vis[x][y]) continue;
vis[x][y]=true;
for(int i=head[x];i;i=nxt[i])
{
int to=ver[i],temp=0;
if(!bit[x][y][to]) temp=s[to];
if(dis[x][y]+temp<dis[to][y])
{
bit[to][y]=bit[x][y];
bit[to][y][to]=true;
dis[to][y]=dis[x][y]+temp;
q.push((Node){dis[to][y],to,y});
}
}
for(int i=head2[y];i;i=nxt2[i])
{
int to=ver2[i],temp=0;
if(!bit[x][y][to]) temp=s[to];
if(dis[x][y]+temp<dis[x][to])
{
bit[x][to]=bit[x][y];
bit[x][to][to]=true;
dis[x][to]=dis[x][y]+temp;
q.push((Node){dis[x][to],x,to});
}
}
}
}
signed main()
{
n=read(); m=read();
for(int i=1;i<=n;i++)
s[i]=read();
for(int i=1,x,y;i<=m;i++)
{
x=read(); y=read();
add_edge(x,y);
add_edge2(y,x);
}
Dij();
printf("%lld",dis[n][n]==INF?-1ll:dis[n][n]);
return 0;
}