NOIP2015提高組複賽 解題報告
阿新 • • 發佈:2019-01-01
Day 1
T1 直接根據題目描述的模擬就可以了,水。 【程式碼】#include<iostream> #include<cstring> #include<cstdio> using namespace std; int n,m,i,j,num; int a[50][50]; int main(){ scanf("%d",&n); m=(n/2)+1; a[1][m]=1; num=1; i=1; j=m; while (num<n*n){ if (i==1&&j!=n) {a[n][j+1]=++num; i=n; ++j;} else if (i!=1&&j==n) {a[i-1][1]=++num; --i; j=1;} else if (i==1&&j==n) {a[i+1][j]=++num; ++i;} else if (i!=1&&j!=n){ if (!a[i-1][j+1]) {a[i-1][j+1]=++num; --i; ++j;} else {a[i+1][j]=++num; ++i;} } } for (int i=1;i<=n;++i){ for (int j=1;j<n;++j) printf("%d ",a[i][j]); printf("%d\n",a[i][n]); } return 0; }
T2 讀了一遍題就看出來是最小環。剛開始忽略了有好幾個聯通塊,大資料跑不對。最後寫了個並查集+dfs,AC。水。 【程式碼】
#include<iostream> #include<cstring> #include<cstdio> #define inf 2100000000 #define N 200005 using namespace std; int n,ans,y; int f[N],next[N],h[N]; bool b[N]; inline int in(){ int x=0; char ch=getchar(); while (ch<'0'||ch>'9') ch=getchar(); while (ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar(); return x; } int find(int x){ if (x==f[x]) return x; f[x]=find(f[x]); return f[x]; } void merge(int x,int y){ int f1=find(x); int f2=find(y); f[f1]=f2; } void dfs(int x,int dep){ b[x]=true; h[x]=dep; if (!b[next[x]]) dfs(next[x],dep+1); else{ int k=h[x]-h[next[x]]+1; ans=min(ans,k); } } int main(){ n=in(); ans=inf; for (int i=1;i<=n;++i) f[i]=i; for (int i=1;i<=n;++i){ y=in(); next[i]=y; if (find(i)!=find(y)) merge(i,y); else{ memset(b,0,sizeof(b)); dfs(i,1); } } printf("%d",ans); return 0; }
T3 時限2s記憶體1G就知道這道題一定很神。剛開始看到了之後一點思路都沒有,以為是dp但是發現狀態太多壓不下。而且還有多組資料一旦跑不對就掛了。30分打表。 後來聽到TA大爺說就是個dfs,把每一種情況編上號之後搜尋就可以了。 好像也有dfs+dp的做法,不過我覺得這個更好理解。 1、單順子 2、雙順子 3、三順子 4、三帶一、三帶二 5、四帶二、四帶兩對 由於每張牌的數量最多有4張,所以能保證這一種牌只要有就一定能一次打出去,所以出牌總次數的上限就是手牌的種類數。而每一次dfs都有一個新的上限,就是當前步數+現有的手牌的種類數。dfs過程中要進行最優化剪枝。 2016.10.28update 這題比較好理解,不過忘得差不多幹淨了之後又重寫了一遍。 覺得現在的碼風比原來好很多,所以把程式碼換掉_(:з」∠)_ 【程式碼】
#include<iostream> #include<cstring> #include<cstdio> using namespace std; int T,n,x,y,ans; int a[20]; void clear() { memset(a,0,sizeof(a)); ans=0; } bool check() { for (int i=1;i<=15;++i) if (a[i]) return false; return true; } void dfs(int dep) { if (dep>ans) return; if (check()) { ans=min(ans,dep); return; } int sum=0; for (int i=1;i<=13;++i) if (a[i]) sum++; if (a[14]+a[15]) sum++; ans=min(ans,dep+sum); // 1 單順子 2 雙順子 3 三順子 4 三帶一、三帶二 5 四帶二、四帶兩對 for (int kind=1;kind<=5;++kind) { if (kind==1) { for (int i=1;i<=8;++i) if (a[i]) { bool flag=true; for (int j=i+1;j<=i+3;++j) if (!a[j]) {flag=false;break;} if (!flag) continue; for (int j=i+4;j<13&&a[j];++j) { for (int k=i;k<=j;++k) --a[k]; dfs(dep+1); for (int k=i;k<=j;++k) ++a[k]; } } } if (kind==2) { for (int i=1;i<=10;++i) if (a[i]>=2&&a[i+1]>=2) for (int j=i+2;j<13&&a[j]>=2;++j) { for (int k=i;k<=j;++k) a[k]-=2; dfs(dep+1); for (int k=i;k<=j;++k) a[k]+=2; } } if (kind==3) { for (int i=1;i<=11;++i) if (a[i]>=3) { if (a[i+1]<3) continue; for (int j=i;j<13&&a[j]>=3;++j) { for (int k=i;k<=j;++k) a[k]-=3; dfs(dep+1); for (int k=i;k<=j;++k) a[k]+=3; } } } if (kind==4) { for (int i=1;i<=13;++i) if (a[i]>=3) { a[i]-=3; for (int j=1;j<=15;++j) if (a[j]) { --a[j]; dfs(dep+1); ++a[j]; } for (int j=1;j<=15;++j) if (a[j]>=2) { a[j]-=2; dfs(dep+1); a[j]+=2; } a[i]+=3; } } if (kind==5) { for (int i=1;i<=15;++i) if (a[i]>=4) { a[i]-=4; for (int j=1;j<=15;++j) if (a[j]) { --a[j]; for (int k=j;k<=15;++k) if (a[k]) { --a[k]; dfs(dep+1); ++a[k]; } ++a[j]; } a[i]+=4; } for (int i=1;i<=15;++i) if (a[i]>=4) { a[i]-=4; for (int j=1;j<=15;++j) if (a[j]>=2) { a[j]-=2; for (int k=j;k<=15;++k) if (a[k]>=2) { a[k]-=2; dfs(dep+1); a[k]+=2; } a[j]+=2; } a[i]+=4; } } } } int main() { scanf("%d%d",&T,&n); while (T--) { clear(); for (int i=1;i<=n;++i) { scanf("%d%d",&x,&y); if (!x) a[y+13]++; if (x==1||x==2) a[x+11]++; if (x>=3) a[x-2]++; } for (int i=1;i<=13;++i) if (a[i]) ans++; if (a[14]+a[15]) ans++; dfs(0); printf("%d\n",ans); } }
Day2
T1 二分答案+貪心列舉,考前做過原題,但是還是調了一會,水。 【程式碼】#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#define N 50005
using namespace std;
int L,n,m,l,r,mid,num;
int a[N];
int ok(int x){
int ans=0,k=0;
for (int i=1;i<=n+1;++i)
if (a[i]-a[k]<x)
ans++;
else k=i;
return ans;
}
int main(){
scanf("%d%d%d",&L,&n,&m);
for (int i=1;i<=n;++i)
scanf("%d",&a[i]);
sort(a+1,a+n+1);
a[0]=0; a[n+1]=L;
l=0; r=L;
while(l<=r){
mid=(l+r)/2;
num=ok(mid);
if (num<=m) l=mid+1;
else r=mid-1;
}
printf("%d",l-1);
}
T2 一眼就能看出來是dp,但是dp學的不好,想了一會沒大有思路,就只寫了30分的部分分。dfs沒跑出來就只得了10分。 動規的思路看了ShallWe的程式碼才勉強理解,要是讓我自己想是絕對想不到的,因為我當時連字首和優化是什麼都不怎麼清楚。 f[i][j][k][l]表示第一個串的前i個,第二個串的前j個,分成k段,還有一維是字首和,也可理解為選還是不選。注意這裡的第一維要加一個滾動陣列。 【程式碼】
#include<iostream>
#include<cstring>
#include<cstdio>
#define N 1005
#define M 205
#define p 1000000007
using namespace std;
char s1[N],s2[M];
int n,m,k;
int f[2][M][M][2];
inline void in(){
char ch=getchar();
while (ch<'a'||ch>'z') ch=getchar();
s1[1]=ch;
for (int i=2;i<=n;++i) s1[i]=getchar();
ch=getchar();
while (ch<'a'||ch>'z') ch=getchar();
s2[1]=ch;
for (int i=2;i<=m;++i) s2[i]=getchar();
}
int main(){
scanf("%d%d%d",&n,&m,&k);
in();
f[0][0][0][0]=1;
f[1][0][0][0]=1;
for (int i=1;i<=n;++i)
for (int j=1;j<=min(i,m);++j)
for (int l=1;l<=k;++l){
int now=i&1,last=(i-1)&1;
if (s1[i]==s2[j]){
f[now][j][l][1]=((f[last][j-1][l-1][0]+f[last][j-1][l-1][1])%p+f[last][j-1][l][1])%p;
f[now][j][l][0]=(f[last][j][l][0]+f[last][j][l][1])%p;
}
else{
f[now][j][l][0]=(f[last][j][l][0]+f[last][j][l][1])%p;
f[now][j][l][1]=0;
}
}
printf("%d",(f[n&1][m][k][0]+f[n&1][m][k][1])%p);
}
2016.10.27update
注意上文的“勉強理解”,今天把當時寫的程式碼翻出來看看然後一臉懵逼,推翻重寫。
可能是最近這種型別的dp做的比較多,現在看這道題就比較簡單了。
同樣是把程式碼先貼出來,詳細題解戳這裡
撒花撒花~~
#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
#define N 1005
#define M 205
#define Mod 1000000007
int n,m,p;
int f[2][N][M],s[2][N][M],g[N][M];
char a[N],b[M];
void init()
{
for (int i=1;i<=n;++i)
for (int j=1;j<=m;++j)
{
g[i][j]=min(i,j);
for (int k=1;k<=min(i,j);++k)
if (a[i-k+1]!=b[j-k+1])
{
g[i][j]=k-1;
break;
}
}
}
int main()
{
scanf("%d%d%d\n",&n,&m,&p);
gets(a+1);gets(b+1);
init();
for (int i=1;i<=m;++i)
for (int j=i;j<=n;++j)
{
f[1][j][i]=f[1][j-1][i];
if (g[j][i]==i) f[1][j][i]++;
s[1][j][i]=s[1][j-1][i-1]+f[1][j][i];
}
for (int i=2;i<=p;++i)
{
memset(f[i&1],0,sizeof(f[i&1]));
memset(s[i&1],0,sizeof(s[i&1]));
for (int j=1;j<=n;++j)
for (int k=1;k<=m;++k)
{
f[i&1][j][k]=f[i&1][j-1][k];
if (g[j][k])
{
int x=s[(i-1)&1][j-1][k-1];
f[i&1][j][k]=(f[i&1][j][k]+s[(i-1)&1][j-1][k-1])%Mod;
int J=max(j-g[j][k]-1,0),K=max(k-g[j][k]-1,0);
int y=s[(i-1)&1][J][K];
f[i&1][j][k]=((f[i&1][j][k]-s[(i-1)&1][J][K])%Mod+Mod)%Mod;
}
s[i&1][j][k]=(s[i&1][j-1][k-1]+f[i&1][j][k])%Mod;
}
}
printf("%d\n",f[p&1][n][m]);
}
T3 第一眼覺得是倍增,然後各種寫,越寫越覺得太繁瑣,好像實現不了。最後只寫了一個鏈,時間複雜度還算錯了,所以只有5分。其實還是有很多部分分的,但是考試時沒有時間寫了。 總的來說,NOIP2015做的還是可以的,簡單一點的題沒有丟分,但是有一些思考複雜度高一點的題目沒有做出來,下一步應該加強訓練。 2016.10.18update 時至今日,懶惰的Po終於把自己過掉的T3貼出來。 詳細題解戳這裡 撒花~~
#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
#define N 300005
#define sz 19
int n,m,x,y,z,Max,dfs_clock,ans;
int tot,point[N],nxt[N*2],v[N*2],c[N*2];
int h[N],dis[N],val[N],num[N],tmp[N],f[N][sz+5];
struct hp{int x,y,lca,dis;}edge[N];
void addedge(int x,int y,int z)
{
++tot; nxt[tot]=point[x]; point[x]=tot; v[tot]=y; c[tot]=z;
++tot; nxt[tot]=point[y]; point[y]=tot; v[tot]=x; c[tot]=z;
}
void build(int x,int fa)
{
num[++dfs_clock]=x;
for (int i=1;i<sz;++i)
{
if ((h[x]-(1<<i))<1) break;
f[x][i]=f[f[x][i-1]][i-1];
}
for (int i=point[x];i;i=nxt[i])
if (v[i]!=fa)
{
f[v[i]][0]=x;
h[v[i]]=h[x]+1;dis[v[i]]=dis[x]+c[i];val[v[i]]=c[i];
build(v[i],x);
}
}
int lca(int x,int y)
{
if (h[x]<h[y]) swap(x,y);
int k=h[x]-h[y];
for (int i=0;i<sz;++i)
if ((1<<i)&k) x=f[x][i];
if (x==y) return x;
for (int i=sz-1;i>=0;--i)
if (f[x][i]!=f[y][i])
x=f[x][i],y=f[y][i];
return f[x][0];
}
bool check(int mid)
{
int cnt=0,limit=0;memset(tmp,0,sizeof(tmp));
for (int i=1;i<=m;++i)
if (edge[i].dis>mid)
{
++tmp[edge[i].x];++tmp[edge[i].y];tmp[edge[i].lca]-=2;
limit=max(limit,edge[i].dis-mid);
cnt++;
}
if (!cnt) return true;
for (int i=n;i>1;--i) tmp[f[num[i]][0]]+=tmp[num[i]];
for (int i=2;i<=n;++i)
if (val[i]>=limit&&tmp[i]==cnt) return true;
return false;
}
int find()
{
int l=0,r=Max,mid,ans;
while (l<=r)
{
mid=(l+r)>>1;
if (check(mid)) ans=mid,r=mid-1;
else l=mid+1;
}
return ans;
}
int main()
{
scanf("%d%d",&n,&m);
for (int i=1;i<n;++i)
{
scanf("%d%d%d",&x,&y,&z);
addedge(x,y,z);Max+=z;
}
build(1,0);
for (int i=1;i<=m;++i)
{
scanf("%d%d",&edge[i].x,&edge[i].y);
edge[i].lca=lca(edge[i].x,edge[i].y);
edge[i].dis=dis[edge[i].x]+dis[edge[i].y]-dis[edge[i].lca]*2;
}
ans=find();
printf("%d\n",ans);
}