noip模擬35[第一次4題·裂了]
noip模擬35 solutions
這是我第一次這麼正式的考四個題,因為這四個題都出自同一個出題人,並不是拼盤拼出來的。
但是考得非常的不好,因為題非常難而且一直想睡覺。。
有好多我根本就不會的演算法,比如說笛卡爾樹,時至今日我還是不會
對我來說好像並不是一個很大的提升,因為好多題我不能只看著題解改出來
總是要問別人。。。。。我好菜啊好菜啊好菜啊
T1 玩遊戲
其實這個題我在考場上的時候已經想到了正解的一半,
一眼看過去這就是個貪心,我就在草稿紙上手推貪心策略,推了半天發現到了最小值就走不動了
沒有發現還可以從左右兩端向中間貪心,於是這個題就做完了
我們從k位置將整個序列分成兩個塊,分別對兩個塊統計字首和
每次尋找下一個比當前值小的值並向這個位置貪心,一直貪心到最小值
再從這個字首和陣列的最後往前貪心,一樣貪到最小值
如果都能到達最小值,那麼就合法,反之不合法。。
AC_code
#include<bits/stdc++.h>
using namespace std;
#define re register int
#define ll long long
const int N=1e5+5;
int T,n,k;
ll a[N],b[N],cb,c[N],cc;
ll prb[N],prc[N];
int nxb[N],nxc[N];
signed main(){
scanf("%d",&T);
while(T--){
/*memset(prb,0,sizeof(prb));
memset(prc,0,sizeof(prc));*/
memset(nxb,0,sizeof(nxb));
memset(nxc,0,sizeof(nxc));
scanf("%d%d",&n,&k);
for(re i=1;i<=n;i++)scanf("%lld",&a[i]);
c[cc=1]=0;for(re i=k+1;i<=n;i++)c[++cc]=a[i];
b[cb=1]=0;for(re i=k;i>1;i--)b[++cb]=a[i];
for(re i=1;i<=cb;i++)prb[i]=prb[i-1]+b[i];
for(re i=1;i<=cc;i++)prc[i]=prc[i-1]+c[i];
ll mn=prb[1],who=1,mb,mc;;
for(re i=2;i<=cb;i++)if(prb[i]<=mn){nxb[who]=i;who=i;mn=prb[i];}
mn=prb[cb];who=cb;
for(re i=cb-1;i>=1;i--)if(prb[i]<mn){nxb[who]=i;who=i;mn=prb[i];}
mn=prc[1];who=1;
for(re i=2;i<=cc;i++)if(prc[i]<=mn){nxc[who]=i;who=i;mn=prc[i];}
mn=prc[cc];who=cc;
for(re i=cc-1;i>=1;i--)if(prc[i]<mn){nxc[who]=i;who=i;mn=prc[i];}
//for(re i=1;i<=cb;i++)cout<<prb[i]<<" ";cout<<endl;
//for(re i=1;i<=cc;i++)cout<<prc[i]<<" ";cout<<endl;
int x=1,y=1;
bool flag=false;
if(prb[cb]+prc[cc]>0){
printf("No\n");continue;
}
while(nxb[x]||nxc[y]){
//cout<<x<<" "<<y<<endl;
flag=false;
for(re i=x+1;i<=nxb[x];i++)
if(prb[i]+prc[y]>0){
flag=true;
break;
}
if(!nxb[x])flag=true;
if(flag){
flag=false;
for(re i=y+1;i<=nxc[y];i++)
if(prb[x]+prc[i]>0){
flag=true;
}
if(!nxc[y])flag=true;
if(flag)break;
else y=nxc[y];
flag=false;continue;
}
x=nxb[x];flag=false;
}
if(flag){
printf("No\n");continue;
}
x=cb;y=cc;
while(nxb[x]||nxc[y]){
//if(x!=0)cout<<x<<" "<<nxb[x]<<" "<<y<<" "<<nxc[y]<<endl;
flag=false;
if(nxb[x]){
//cout<<nxb[x]<<endl;
for(re i=x-1;i>=nxb[x];i--)
if(prb[i]+prc[y]>0){
flag=true;
break;
}
}
else flag=true;
//cout<<flag<<endl;
if(flag){
flag=false;
if(nxc[y]){
for(re i=y-1;i>=nxc[y];i--)
if(prb[x]+prc[i]>0){
flag=true;
}
}
else flag=true;
//cout<<flag<<endl;
if(flag)break;
else y=nxc[y];
flag=false;continue;
}
x=nxb[x];flag=false;
}
if(flag){
printf("No\n");continue;
}
printf("Yes\n");
}
}
T2 排列
這個我是真的不知道怎麼做,所以直接棄掉了。
所以說我已經發現了一個規律,對於這種隨機搞出來的方案題
一般都是直接dp,而且是啥都不在乎的dp,就暴力轉移就完事了
其實對於這個題我們完全可以直接全排列做的。。。。。。。爬
設dp[i][j][0/1][0/1]表示前i個點,消了j次能夠只剩1個的方案數
你發現我這個東西轉移的時候只能去分別列舉左右區間需要的次數,
這樣的話,你就得有四層迴圈,一層列舉i,一層列舉j,一層列舉當前層的最大值位置
還有一層列舉兩個子區間的j,
虛擬碼
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
for(int k=1;k<=n;k++)
for(int jj=1;jj<j;j++){
dp[i][j][0/1][0/1]=dp[k-1][j-1][0/1][0/1]*dp[i-k][jj][0/1][0/1];
dp[i][j][0/1][0/1]=dp[k-1][jj][0/1][0/1]*dp[i-k][j-1][0/1][0/1];
}
此處省略許多細節。。。。。看AC_code
這樣的話,你不T誰T,TTTTTTTTTTTTTTTTTTTTTTTTT飛了好吧
這樣你發現這個轉移好像並不是那麼嚴謹,許多的轉移都是同一個形式,
字首和優化就是最好的選擇,畢竟方程都是差不多的,
那麼字首和優化之後的方程就表示至多消j步的方案數,轉移仍然是一樣的
AC_code
#include<bits/stdc++.h>
using namespace std;
#define re register int
#define ll long long
const int N=1005;
ll n,m,mod;
ll dp[N][N][2][2];
ll c[N][N];
signed main(){
scanf("%lld%lld%lld",&n,&m,&mod);
for(re i=0;i<=m;i++)dp[0][i][0][0]=dp[0][i][0][1]=dp[0][i][1][0]=dp[0][i][1][1]=1;
c[0][0]=1;c[1][0]=1;c[1][1]=1;
for(re i=2;i<=n;i++){
c[i][0]=1;c[i][i]=1;
for(re j=1;j<i;j++){
c[i][j]=(c[i-1][j]+c[i-1][j-1])%mod;
//cout<<c[i][j]<<" ";
}
//cout<<endl;
}
for(re i=1;i<=n;i++){
for(re j=1;j<=m;j++){
for(re k=1;k<=i;k++){
dp[i][j][0][0]=(dp[i][j][0][0]+dp[k-1][j][0][1]*dp[i-k][j][1][0]%mod*c[i-1][k-1])%mod;
dp[i][j][0][1]=(dp[i][j][0][1]+dp[k-1][j][0][1]*dp[i-k][j-1][1][1]%mod*c[i-1][k-1])%mod;
dp[i][j][1][0]=(dp[i][j][1][0]+dp[k-1][j-1][1][1]*dp[i-k][j][1][0]%mod*c[i-1][k-1])%mod;
dp[i][j][1][1]=(dp[i][j][1][1]+(dp[k-1][j][1][1]*dp[i-k][j][1][1]-(dp[i-k][j][1][1]-dp[i-k][j-1][1][1])*(dp[k-1][j][1][1]-dp[k-1][j-1][1][1])%mod)%mod*c[i-1][k-1])%mod;
}
}
}
ll ans=(dp[n][m][0][0]+mod-dp[n][m-1][0][0])%mod;
printf("%lld",ans);
}
T3 最短路
這個題我好像是用yubai的亂搞做法過去的,好像也是正解,反正跑的還挺快
簡單來說就是一個分層圖,也不能說是分層,就是兩個點一起走
然後對於重複的點來說就直接用一個bitset維護就好了,其實bool陣列也是可以的
好像正確性並不能保證,對於當前的兩個點來說後面可能會經過一個點
使得當前的點的較大值能夠得到最優解,所以這個題我就棄了
放上我的假程式碼。。。。
在隔壁Varuxn的幫助下,我成功的找到了正確性
因為我的dij中每一步都是單一的一個點向前跳,這樣的話我就保證了一個點不動,另外一個點走完全程
這樣我就可以遍歷到所有的狀態
AC_code
#include<bits/stdc++.h>
using namespace std;
#define re register int
const int N=255;
const int M=255*255;
int n,m,val[N];
struct EDGE{
int to[M],nxt[M],head[N],rp;
EDGE(){}
void add_edg(int x,int y){
to[++rp]=y;
nxt[rp]=head[x];
head[x]=rp;
}
}be,af;
int ans[N][N];
bitset<N> bit[N][N];
struct node{
int x,y,dis;
node(){}
node(int a,int b,int c){
x=a;y=b;dis=c;
}
bool operator < (node a)const{
return dis>a.dis;
}
};
priority_queue<node> q;
bool vis[N][N];
void dij(){
bit[1][1].set(1);
ans[1][1]=val[1];
q.push(node(1,1,val[1]));
while(!q.empty()){
int x=q.top().x;
int y=q.top().y;
int dis=q.top().dis;q.pop();
if(vis[x][y])continue;
vis[x][y]=true;
//cout<<x<<" "<<y<<" "<<dis<<endl;
for(re i=be.head[x];i;i=be.nxt[i]){
int tx=be.to[i];
int tmp=dis;
if(bit[x][y][tx]==0)tmp+=val[tx];
if(tmp>=ans[tx][y])continue;
bit[tx][y]=bit[x][y];
bit[tx][y].set(tx);
ans[tx][y]=tmp;
q.push(node(tx,y,tmp));
}
for(re i=af.head[y];i;i=af.nxt[i]){
int ty=af.to[i];
int tmp=dis;
if(bit[x][y][ty]==0)tmp+=val[ty];
if(tmp>=ans[x][ty])continue;
bit[x][ty]=bit[x][y];
bit[x][ty].set(ty);
ans[x][ty]=tmp;
q.push(node(x,ty,tmp));
}
}
}
signed main(){
scanf("%d%d",&n,&m);
for(re i=1;i<=n;i++)scanf("%d",&val[i]);
for(re i=1;i<=m;i++){
int x,y;scanf("%d%d",&x,&y);
be.add_edg(x,y);
af.add_edg(y,x);
}
memset(ans,0x3f,sizeof(ans));
dij();//cout<<ans[n][n]<<endl;
if(ans[n][n]==0x3f3f3f3f)printf("-1");
else printf("%d",ans[n][n]);
}
T4 矩形
我走了,我走了,我不會,我寫不出來,我根本不會打。。。。。