题目见此
day1
神奇的幻方:
裸裸的模拟(其实也可以发现规律:i+1在i的右上方,如果已经有数了,就填在i的下方)
参考程序:
#include
#include
using namespace std;
int a[50][50];
int nx,ny,n;
int main(){
freopen("magic.in","r",stdin);
freopen("magic.out","w",stdout);
scanf("%d",&n);
nx=1;ny=n/2+1;
a[nx][ny]=1;
for (int i=2;i<=n*n;i++){
int tx,ty;
if (nx==1){
if (ny!=n){
tx=n;ty=ny+1;
}else{
tx=nx+1;ty=ny;
}
}else{
if (ny==n){
tx=nx-1;ty=1;
}else{
tx=nx-1;ty=ny+1;
if (a[tx][ty]){tx=nx+1;ty=ny;}
}
}
a[tx][ty]=i;
nx=tx;ny=ty;
}//紧跟题目走,比较low的程序
for (int i=1;i<=n;i++){
for (int j=1;j<=n;j++)
printf("%d ",a[i][j]);
printf("\n");
}
return 0;
}
信息传递:
当时比赛时用Pascal打了个tarjan求强连通分量,不知为什么爆栈40分,心痛心痛
所以这次打了个简单的深搜,其实就是沿着走,如果当前的点已经访问过了就是一个环。
参考程序:
#include
#include
#include
#include
#define maxn 210000
using namespace std;
int a[maxn];
int ans[maxn];
int res=0x7f7f7f7f;
bool vis[maxn];
int n;
void dfs(int x,int k){
ans[x]=k;
vis[x]=true;
if (!vis[a[x]])dfs(a[x],k+1);
if (ans[a[x]] && ans[x]-ans[a[x]]+1)
res=min(res,ans[x]-ans[a[x]]+1);
ans[x]=0;
}
int main(){
freopen("message.in","r",stdin);
freopen("message.out","w",stdout);
scanf("%d",&n);
memset(vis,0,sizeof(vis));
memset(ans,0,sizeof(ans));
for (int i=1;i<=n;i++)
scanf("%d",&a[i]);
for (int i=1;i<=n;i++)
if (!vis[i])dfs(i,1);
printf("%d",res);
//printf("%d\n%.2f",res,(double)clock()/CLOCKS_PER_SEC);
return 0;
}
斗地主:
又是深搜(好像几年的NOIP的压轴题都是倍增和深搜。。)
其实,每次深搜时只需记录当前的步数和剩下的牌,对于每一个状态首先模拟不打顺子的情况最少步数(无需深搜,直接计算),再深搜需要打顺子的情况,加个最优化剪枝就行了,不是非常复杂。
参考程序:
#include
#include
#include
using namespace std;
int n;
int hand[25];
int sum[25];
int ans;
int unline(){
memset(sum,0,sizeof(sum));
for (int i=0;i<=13;i++)
sum[hand[i]]++;
int tot=0;
while(sum[4]&&sum[2]>1) sum[4]--,sum[2]-=2,tot++;
while(sum[4]&&sum[1]>1) sum[4]--,sum[1]-=2,tot++;
while(sum[4]&&sum[2]) sum[4]--,sum[2]--,tot++;
while(sum[3]&&sum[2]) sum[3]--,sum[2]--,tot++;
while(sum[3]&&sum[1]) sum[3]--,sum[1]--,tot++;
return tot+sum[1]+sum[2]+sum[3]+sum[4];
}
void dfs(int dep){
if (dep>=ans)return;
int tmp=unline();
if (tmp+dep=3) j++;
if(j-i>=2) {
for (int j2=i+1;j2<=j-1;j2++) {
for (int k=i;k<=j2;k++) hand[k]-=3;
dfs(dep+1);
for (int k=i;k<=j2;k++) hand[k]+=3;
}
}
}
for (int i=2;i<=13;i++) {
int j=i;
while(hand[j]>=2) j++;
if(j-i>=3) {
for (int j2=i+2;j2<=j-1;j2++) {
for (int k=i;k<=j2;k++) hand[k]-=2;
dfs(dep+1);
for (int k=i;k<=j2;k++) hand[k]+=2;
}
}
}
for (int i=2;i<=13;i++) {
int j=i;
while(hand[j]>=1) j++;
if(j-i>=5) {
for (int j2=i+4;j2<=j-1;j2++) {
for (int k=i;k<=j2;k++) hand[k]--;
dfs(dep+1);
for (int k=i;k<=j2;k++) hand[k]++;
}
}
}
}
int main(){
freopen("landlords.in","r",stdin);
freopen("landlords.out","w",stdout);
int T;
scanf("%d%d",&T,&n);
while (T--){
memset(hand,0,sizeof(hand));
for (int i=1;i<=n;i++){
int x,y;
scanf("%d%d",&x,&y);
if(x==1) x=13; else if(x) x--;
hand[x]++;
}
ans=0x7f7f7f7f;
dfs(0);
printf("%d\n",ans);
}
return 0;
}
day2:
跳石头:
此题在poj之旅中应做过,二分即可,比赛当时因为r上限取错了,30分没了,心痛心痛。
建立模型(l,r]区间,取mid贪心判断,(贪心:如果当前石头比上一个选取剩下的石头的距离小于mid就把当前石头移走,最后若移走的石头数大于m,就说明mid过大,r=mid,否则l=mid。
参考程序:
#include
#include
#define maxn 110000
using namespace std;
int n,m,L;
int a[maxn];
bool check(int k){
int j=0,cnt=0;
for (int i=1;i<=n;i++)
if (a[i]-a[j]>1;
if (check(mid))l=mid;
else r=mid;
}
printf("%d",l);
return 0;
}
子串:
其实就是dp,令f[i][j][k]表示A串选到第i个位置(第i个字母要选),B串选到第j个位置,共用了k个子串的方案数
则若a[i]!=b[j] , f[i][j][k]=0;
a[i]==b[j],f[i][j][k]=f[i-1][j-1][k](和前一个连成一个子串)+f[p][j-1][k-1](0
显然,既超时又超空间。 对于f[p][j-1][k-1]求和,我们预处理sum[i][j-1][k-1]=f[1][j-1][k-1]+.....f[i][j-1][k-1],于是只需O(1)时间转移,总效率O(nmk) 再发现f[][][k]只于f[][][k-1]和sum[][][k-1]有关,滚动数组即可。 虽然已满分,但是显然很麻烦, 我们重新定义f[i][j][k][0]表示A串选到第i个位置(第i个字母不选),B串选到第j个位置,共用了k个子串的方案数 f[i][j][k][1]表示A串选到第i个位置(第i个字母要选),B串选到第j个位置,共用了k个子串的方案数 原理同上,可推出(t=i&1,p=1-t---->滚动数组): f[k][t][j][1]=((f[k-1][p][j-1][0]+f[k-1][p][j-1][1])+f[k][p][j-1][1]); f[k][t][j][0]=(f[k][p][j][0]+f[k][p][j][1]); 参考程序:
运输计划 此题最好在BZOJ上测,其他oj都会爆栈。 显然直接求最大值不容易,所以我们可以二分结果,取mid 对于一些线路所需时间小于mid的,我们可以不用管,对于大于mid的,应取这些线路的最大公共线段,如果将这一段改为虫洞仍不能满足要求,则mid过小,调整区间,反之同理。 但我们还是需要预处理,以1为根建立剖分树(剖分法求LCA),然后求问题中线路起点终点对应的LCA和无虫洞时所需的时间,然后就是二分了。 参考程序:
#include
#include