神奇的幻方 模拟
#include<cstdio>
const int N=40;
int a[N][N],num=1,n,x,y;
int main()
{
scanf("%d",&n);
while(num<=n*n)
{
if(num==1)a[x=1][y=(n+1)/2]=num++;
else if(x==1&&y!=n)a[x=n][y=y+1]=num++;
else if(x!=1&&y==n)a[x=x-1][y=1]=num++;
else if(x==1&&y==n)a[x=x+1][y=y]=num++;
else if(!a[x-1][y+1])a[x=x-1][y=y+1]=num++;
else a[x=x+1][y=y]=num++;
}
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
printf("%d%c",a[i][j],j==n?'\n':' ');
return 0;
}
总结
模拟
信息传递 并查集
求最小环。可以通过并查集判断是否成环了。统计环长度可以再写一个无路径压缩的并查集,暴力的跳上去找。
#include<cstdio>
#include<algorithm>
using namespace std;
const int N=2e5+10;
inline int read()
{
int s=0,f=0;char ch=getchar();
while(ch<'0'||ch>'9')f|=ch=='-',ch=getchar();
while(ch>='0'&&ch<='9')s=(s<<1)+(s<<3)+(ch^48),ch=getchar();
if(f)s=-s;return s;
}
int n,t[N],fa[N],nx[N],ans=0x3f3f3f3f;
inline int find(int x){return x==fa[x]?x:fa[x]=find(fa[x]);}
int main()
{
//freopen("in.txt","r",stdin);
n=read();
for(int i=1;i<=n;i++)
nx[i]=fa[i]=i;
for(int i=1;i<=n;i++)
{
t[i]=read();
int fx=find(i),fy=find(t[i]);
if(fx==fy)
{
int cnt=1;
for(int j=t[i];j!=i;j=nx[j])cnt++;
ans=min(ans,cnt);
}
nx[i]=t[i];
fa[fx]=fy;
}
printf("%d\n",ans);
return 0;
}
总结
并查集的应用。
斗地主(增强版) 线性DP+dfs
终于看明白了大佬题解,然而还是各种错误。
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define _rep(i,a,b) for(int i=(a);i<=(b);i++)
int t,n,ans,dp[25][25][25][25][3],p[25],cnt[6];
//dp[a][b][c][d][e],a个单张,b个两张,c个三张,d个四张,e个王的最小出牌次数
void Init()//dp预处理出最小操作次数
{
memset(dp,0x3f,sizeof(dp));dp[0][0][0][0][0]=0;
_rep(d,0,n)_rep(c,0,n)_rep(a,0,n)_rep(b,0,n)_rep(e,0,2)//循环顺序很重要
{
int tmp=100;
if(a>0)tmp=min(tmp,dp[a-1][b][c][d][e]+1);
if(b>0)tmp=min(tmp,dp[a][b-1][c][d][e]+1);
if(c>0)tmp=min(tmp,dp[a][b][c-1][d][e]+1);
if(d>0)tmp=min(tmp,dp[a][b][c][d-1][e]+1);
if(e>0)tmp=min(tmp,dp[a][b][c][d][e-1]+1);
//先走单张
if(e>1)tmp=min(tmp,dp[a][b][c][d][e-2]+1);
//王炸
if(a>0&&c>0)tmp=min(tmp,dp[a-1][b][c-1][d][e]+1);
if(c>0&&e>0)tmp=min(tmp,dp[a][b][c-1][d][e-1]+1);
//三带一
if(b>0&&c>0)tmp=min(tmp,dp[a][b-1][c-1][d][e]+1);
//三带二
if(a>1&&d>0)tmp=min(tmp,dp[a-2][b][c][d-1][e]+1);
if(a>0&&d>0&&e>0)tmp=min(tmp,dp[a-1][b][c][d-1][e-1]+1);
if(d>0&&e>1)tmp=min(tmp,dp[a][b][c][d-1][e-2]+1);
if(b>0&&d>0)tmp=min(tmp,dp[a][b-1][c][d-1][e]+1);
if(b>1&&d>0)tmp=min(tmp,dp[a][b-2][c][d-1][e]+1);
if(d>1)tmp=min(tmp,dp[a][b][c][d-2][e]+1);
//四带二
if(c>0)tmp=min(tmp,dp[a+1][b+1][c-1][d][e]);
if(d>0)tmp=min(tmp,dp[a+1][b][c+1][d-1][e]);
//拆牌
dp[a][b][c][d][e]=min(dp[a][b][c][d][e],tmp);
}
}
void dfs(int step)
{
if(step>=ans)return;
int len;bool flag;
_rep(k,1,3)_rep(i,1,12)
{
flag=true;
if(k==1)len=5;//顺子
if(k==2)len=3;//连对
if(k==3)len=2;//飞机
while(flag&&i+len-1<=12)
{
_rep(j,1,len)if(p[i+j-1]<k){flag=false;break;}
if(!flag)continue;
_rep(j,1,len)p[i+j-1]-=k;
dfs(step+1);
_rep(j,1,len)p[i+j-1]+=k;
len++;//尝试更长的顺子连对飞机
}
}
cnt[1]=cnt[2]=cnt[3]=cnt[4]=cnt[5]=0;_rep(i,1,13)cnt[p[i]]++;
cnt[5]=p[14];
ans=min(ans,step+dp[cnt[1]][cnt[2]][cnt[3]][cnt[4]][cnt[5]]);
}
int main()
{
//freopen("in.txt","r",stdin);
scanf("%d%d",&t,&n);
Init();
while(t--)
{
memset(p,0,sizeof(p));ans=n;
_rep(i,1,n)
{
int num,col;scanf("%d%d",&num,&col);
if(num==0){p[14]++;continue;}
if(num>=3)p[num-2]++;
if(num==2)p[13]++;
if(num==1)p[12]++;
}
dfs(0);printf("%d\n",ans);
}
return 0;
}
总结
牛批的爆搜
跳石头 二分
挺水一题,二分跳跃的最小距离
#include<cstdio>
#define _rep(i,a,b) for(int i=(a);i<=(b);i++)
const int N=5e4+10;
int L,m,n;
int d[N];
bool judge(int lim)
{
int cnt=0,pre=0;
_rep(i,1,n)
{
if(d[i]-pre<lim){cnt++;continue;}
pre=d[i];
}
return cnt<=m;
}
int main()
{
//freopen("in.txt","r",stdin);
scanf("%d%d%d",&L,&n,&m);
_rep(i,1,n)scanf("%d",&d[i]);
int l=1,r=L;
while(l<r)
{
int mid=(l+r+1)>>1;
if(judge(mid))l=mid;
else r=mid-1;
}
printf("%d\n",l);
return 0;
}
子串 线性DP+前缀和
#include<cstdio>
#define _rep(i,a,b) for(int i=(a);i<=(b);i++)
#define rep_(i,a,b) for(int i=(a);i>=(b);i--)
const int mod=1e9+7;
int dp[201][201],sum[201][201],n,m,K;
char a[1001],b[1001];
int main()
{
scanf("%d%d%d%s%s",&n,&m,&K,a+1,b+1);dp[0][0]=1;
_rep(i,1,n)rep_(j,m,1)rep_(k,K,1)dp[j][k]=(dp[j][k]+((a[i]==b[j])?(sum[j][k]=(sum[j-1][k]+dp[j-1][k-1])%mod):(sum[j][k]=0)))%mod;
printf("%d\n",dp[m][K]);return 0;
}
总结
无
运输计划 树链剖分+树上差分+LCA+二分
学习了大佬题解,主要思路摘抄如下:
先LCA一遍,记下每个任务的起点,终点,公共祖先,所需时间
然后二分答案,统计不满足答案的任务tot,然后维护一个sum[i],
对于每个不满足条件的任务,sum[起点]++,sum[终点]++,sum[公共祖先]-=2,
并将它们的sum值传到父亲结点,最后看是否能找出某个点i,使sum[i]=tot并且
连到这个点的边权值>= 最大任务时间-答案,如果能,这个答案即为可行答案。
感觉就是标记每个点经过多少不满足条件的边,然后看这个点连向父节点的边权改为0后能不能满足条件
自己在模拟的时候打的是暴力
#include<cstdio>
#include<algorithm>
#include<queue>
#include<cstring>
using namespace std;
template<typename tp>inline void read(tp &x)
{
int f=0;char ch=getchar();x=0;
while(ch<'0'||ch>'9')f|=ch=='-',ch=getchar();
while(ch>='0'&&ch<='9')x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
if(f)x=-x;
}
const int N=3e5+10;
int n,m,tot=1,hd[N],maxn,dis[N],dep[N],fa[N],ans=0x7fffffff,sz[N],top[N],son[N],edge[N],INF,sum[N];
struct Edge{
int v,nx,w;
}e[N<<1];
struct node{
int u,v;
}ask[N];
struct Node{
int u,v,anc,dis;
}lca[N];
inline void add(int u,int v,int w)
{
e[++tot].v=v;
e[tot].w=w;
e[tot].nx=hd[u];
hd[u]=tot;
}
namespace pts20{
void dfs(int u,int f)
{
fa[u]=f;
for(int i=hd[u];i;i=e[i].nx)
{
int v=e[i].v;if(v==f)continue;
dis[v]=dis[u]+e[i].w;
dfs(v,u);
}
}
void dfs2(int u,int son)
{
for(int i=hd[u];i;i=e[i].nx)
{
int v=e[i].v;
if(v==son)continue;
if(v==fa[u]){maxn=max(maxn,e[i].w),dfs2(v,u);break;}
}
}
inline void solve()
{
int st,ed;read(st);read(ed);
dfs(st,0);dfs2(ed,ed);printf("%d\n",dis[ed]-maxn);
}
}
namespace pts30{//在学校OJ能得30分
inline void dij(int st,int ed)
{
priority_queue<pair<int,int>,vector<pair<int,int> >,greater<pair<int,int> > >q;while(!q.empty())q.pop();q.push(make_pair(0,st));
memset(dis,0x3f,sizeof(dis));dis[st]=0;static int vis[N];memset(vis,0,sizeof(vis));
while(!q.empty())
{
int u=q.top().second;q.pop();vis[u]=0;
for(int i=hd[u];i;i=e[i].nx)
{
int v=e[i].v;
if(dis[v]>dis[u]+e[i].w)
{
dis[v]=dis[u]+e[i].w;
if(!vis[v])vis[v]=1,q.push(make_pair(dis[v],v));
}
}
}
maxn=max(maxn,dis[ed]);
}
inline void solve()
{
for(int i=1;i<=m;i++)
read(ask[i].u),read(ask[i].v);
for(int i=2;i<=tot;i+=2)
{
int tmp=e[i].w;e[i].w=e[i^1].w=0;
maxn=0;
for(int j=1;j<=m;j++)
dij(ask[j].u,ask[j].v);
ans=min(ans,maxn);
e[i].w=e[i^1].w=tmp;
}
printf("%d\n",ans);
}
}
namespace ac{
void dfs1(int u,int f)
{
dep[u]=dep[f]+1;fa[u]=f;sz[u]=1;
int maxson=-1;
for(int i=hd[u];i;i=e[i].nx)
{
int v=e[i].v;
if(v==f)continue;
edge[v]=i;dis[v]=dis[u]+e[i].w;
dfs1(v,u);sz[u]+=sz[v];
if(sz[v]>maxson)son[u]=v,maxson=sz[v];
}
}
void dfs2(int u,int topf)
{
top[u]=topf;
if(son[u])dfs2(son[u],topf);
for(int i=hd[u];i;i=e[i].nx)
{
int v=e[i].v;
if(v==son[u]||v==fa[u])continue;
dfs2(v,v);
}
}
inline int LCA(int x,int y)//树剖求LCA
{
while(top[x]!=top[y])
{
if(dep[top[x]]<dep[top[y]])swap(x,y);//x顶端深度更深
x=fa[top[x]];//x跳到x所在链顶端上面一个点
}
if(dep[x]>dep[y])swap(x,y);
return x;
}
void dfs3(int u,int f)//求子树中差分数组和
{
for(int i=hd[u];i;i=e[i].nx)
{
int v=e[i].v;
if(v==f)continue;
dfs3(v,u);sum[u]+=sum[v];
}
}
inline bool check(int lim)
{
memset(sum,0,sizeof(sum));
int p=0,cnt=0;
for(int i=1;i<=m;i++)
if(lca[i].dis>lim)
{
cnt++;
sum[lca[i].u]++;
sum[lca[i].v]++;
sum[lca[i].anc]-=2;
p=max(p,lca[i].dis-lim);
}
dfs3(1,1);
for(int i=1;i<=n;i++)
if(sum[i]==cnt&&e[edge[i]].w>=p)return 1;
return 0;
}
inline void solve()
{
dfs1(1,0);dfs2(1,1);
for(int i=1;i<=m;i++)
{
read(lca[i].u);read(lca[i].v);
lca[i].anc=LCA(lca[i].u,lca[i].v);
lca[i].dis=dis[lca[i].u]+dis[lca[i].v]-2*dis[lca[i].anc];
INF=max(INF,lca[i].dis);
}
int l=0,r=INF,ans=0;
while(l<=r)
{
int mid=(l+r)>>1;
if(check(mid))ans=mid,r=mid-1;
else l=mid+1;
}
printf("%d\n",ans);
}
}
int main()
{
//freopen("in.txt","r",stdin);
read(n);read(m);
for(int i=1,u,v,w;i<n;i++)
read(u),read(v),read(w),add(u,v,w),add(v,u,w);
//if(m==1)pts20::solve();
//else pts30::solve();
ac::solve();
return 0;
}
总结
综合性很大的题。树剖求LCA的操作还没见过。