NOI 2003 题解

木棒游戏

(传送门)

题意

木棒拼成的等式,移动一根木棒,使等式成立

分析

傻逼的模拟题

移动一根火柴可以分为两种情况:

一是移动某一个数字上的一根火柴,使之变为另一个数字。

二是两个数字,一个添加一根火柴,一个减少一根火柴。

弄清楚了原理就多重循环模拟吧

代码

#include <bits/stdc++.h>
using namespace std;

const int MAXN=1010;
char s[MAXN];
int n,m,i,j,I,J,k,t,a[10][10],b[10][10],ppow[8],v[MAXN],w[MAXN],e[MAXN][8],l[MAXN],pos;
long long sum,tmp;

void ok()
{
    for(i=1;i<=pos;i++)
    {
        if(i>1) putchar(w[i]==1?'+':'-');
        else if(w[i]!=1)putchar('-');
        for(j=l[i]-1;~j;j--)
            printf("%d",e[i][j]);
    }
    for(putchar('=');i<=n;i++)
    {
        if(i>pos+1) putchar(w[i]==1?'-':'+');
        else if(w[i]==1)putchar('-');
        for(j=l[i]-1;~j;j--)
            printf("%d",e[i][j]);
    }
    putchar('#');
    exit(0);
}

void _do()
{
    (v[n]*=10)+=s[i]-'0';
    e[n][l[n]++]=s[i]-'0';
}

int main()
{
    for(ppow[0]=i=1;i<8;i++)
        ppow[i]=ppow[i-1]*10;
    a[0][6]=a[0][9]=a[2][3]=a[3][2]=a[3][5]=a[5][3]=a[6][0]=a[6][9]=a[9][0]=a[9][6]=1
    b[0][8]=b[1][7]=b[3][9]=b[5][6]=b[5][9]=b[6][8]=b[9][8]=1;
    
    scanf("%s",s+1);
    n=strlen(s+1);
    
    if(s[1]=='-')
        for(w[n=1]=-1,i=2;s[i]>='0'&&s[i]<='9';i++) _do();
    else 
        for(w[n=1]=i=1;s[i]>='0'&&s[i]<='9';i++) _do();
    
    while(s[i]!='=')
        for(w[++n]=s[i++]=='+'?1:-1;s[i]>='0'&&s[i]<='9';i++) _do();

    pos=n;
    
    if(s[++i]=='-')
        for(w[++n]=1,i++;s[i]>='0'&&s[i]<='9';i++) _do();
    else 
        for(w[++n]=-1;s[i]>='0'&&s[i]<='9';i++) _do();
    
    while(s[i]!='#')
      for(w[++n]=s[i++]=='-'?1:-1;s[i]>='0'&&s[i]<='9';i++) _do();
 
    for(i=1;i<=n;i++)
        for(sum+=v[i]*w[i],j=0,k=l[i]-1;j<k;j++,k--)
            swap(e[i][j],e[i][k]);

    for(i=1;i<=n;i++)
        for(j=0;j<l[i];j++)
            for(t=0;t<=9;t++)
                if(a[e[i][j]][t])
                {
                    tmp=sum+w[i]*(t-e[i][j])*ppow[j];
                    if(!tmp)e[i][j]=t,ok();
                }
    for(i=1;i<=n;i++)
        for(j=0;j<l[i];j++)
            for(I=1;I<=n;I++)
                for(J=0;J<l[I];J++)
                    if(!(i==I&&j==J))
                        for(k=0;k<=9;k++)
                            for(t=0;t<=9;t++)
                                if(b[e[i][j]][k]&&b[t][e[I][J]])
                                {
                                    tmp=sum+w[i]*(k-e[i][j])*ppow[j]+w[I]*(t-e[I][J])*ppow[J];
                                    if(!tmp)e[i][j]=k,e[I][J]=t,ok();
                                }
    return puts("No"),0;
}

文本编辑器

(传送门)

题意

对于给定的要求,支持光标的跳转,前移后移,及某处开始的插入,删除,输出操作

分析

看到一大堆操作,数据结构题,又看到了这个题有可以跳转什么的,splay可搞定
但是没有翻转之类的操作,写splay显然有些大材小用了,STL中的块状链表也可以完美的解决(简直就是bug)。

代码

#include <iostream>
#include <cstdio>
#include <ext/rope>
using namespace std;
using namespace __gnu_cxx;
crope list;

char ch[3000005];

int main()
{
	int t,x,now=0;
    cin>>t;
    char s[10];
    while(t--)
    {
        scanf("%s",s);
        switch(s[0])
        {
        case 'M': scanf("%d",&now); break;
        case 'P': now--; break;
        case 'N': now++; break;
        case 'D': scanf("%d",&x); list.erase(now,x); break;
        case 'G': scanf("%d",&x); list.copy(now,x,ch); ch[x]=0; printf("%s\n",ch); break;
		case 'I':
            scanf("%d",&x);
            for(int i=0;i<x;i++)
            {
                ch[i]=getchar();
                while(ch[i]=='\n')ch[i]=getchar();
            }
            ch[x]=0;
            list.insert(now,ch);
		}
    }
    return 0;
}

数据生成器

(传送门)

题意

从一棵树中找出三个点A,B,C,使得min(dis[A][C],dis[B][C])+dis[A][B]最大,求这个最大值

分析

这道题显然是Tree_Dp问题,在这儿可以想象别的方法
仔细想想,当min(dis[A][C],dis[B][C])+dis[A][B]取最大值时,路径AB是整个树的直径(最长链),
所以可以通过BFS找出树的直径后,直接枚举点C即可得到最大答案。
这样虽然稍微慢了一些,但无疑好想又好写。

代码

#include <bits/stdc++.h>
using namespace std;

const int MAXN=200000+5;
struct Edge
{
    int u,v,w,next;
}edges[MAXN*2];
int head[MAXN],tot=0;

void addEdge(int u,int v,int w)
{
    edges[++tot]=(Edge){u,v,w,head[u]};
    head[u]=tot;
}

int n,m;
int BFS(int S,long long dist[])//求出每个点到S的距离,保存在dist[]数组里,并返回距离S最远的点
{
    int ans=0;//最远点
	bool vis[MAXN];
    memset(vis,0,sizeof(vis));
    int Q[MAXN],h=0,t=1;
    dist[S]=0,Q[h]=S,vis[S]=1;

    while(h<t)
    {
        int u=Q[h++];
        for(int p=head[u];p!=-1;p=edges[p].next)
        {
            int v=edges[p].v;
            if(vis[v]) continue;
            dist[v]=dist[u]+edges[p].w;
            if(dist[v]>dist[ans]) ans=v;
            Q[t++]=v;
            vis[v]=2;
        }
    }
    return ans;
}

long long dista[MAXN],distb[MAXN];
int main()
{
    memset(head,-1,sizeof(head));
    cin>>n>>m;
    for(int i=1;i<=m;i++)
    {
        int u,v,w;
        scanf("%d%d%d",&u,&v,&w);
        addEdge(u,v,w);
        addEdge(v,u,w);
    }
    int a=BFS(1,dista);
    int b=BFS(a,dista);
    BFS(b,distb);
    long long ans=0;
    for(int i=1;i<=n;i++)
        ans=max(ans,min(dista[i],distb[i]));
    ans+=dista[b];
    cout<<ans<<endl;
    return 0;
}

智破连环阵

(传送门)

题意

给出地图上N个目标点,M台武器(只能用一次)。每个武器启动后,将距它距离不超过K范围内的符合要求的目标全部摧毁
(要求:摧毁目标必须按编号顺序,而且同一时刻只有一个武器是启动的),求摧毁全部目标需要动多少武器

分析

部分搜索+二分图匹配,模型应该很好想

代码

#include <bits/stdc++.h>
using namespace std;

const int MAXN=100+10;
const int INF=0x3f3f3f3f;
int n,m,f,point=0,res=INF;
int pos[MAXN][2],wea[MAXN][2],m[MAXN][MAXN],map[MAXN][MAXN],cov[MAXN],x[MAXN][MAXN],dis[MAXN];
bool bomb[MAXN][MAXN],len[MAXN][MAXN],vis[MAXN];

int dist(int a,int b)
{
    return a*a+b*b;
}

void Add(int u,int v)
{
    for(int i=1;i<=m;i++)
        if(m[i][u]==v)
            x[point][++x[point][0]]=i;
}

int Hungary(int u)
{
    if(vis[u]) return 0;
    vis[u]=1;
    for(int i=1;i<=x[u][0];i++)
    {
        int v=x[u][i];
        if(!cov[v] || Hungary(cov[v]))
        {
            cov[v]=u;
            return v;
        }
    }
    return 0;
}

void dfs(int i)
{
    if(i>n) res=min(res,point);
    if(point+dis[i]>=res) return;
    point++;
    x[point][0]=0;
    for(int u=map[i][0];u>0;u--)
    {
        int t=map[i][u];
        Add(i,t);
        memset(vis,0,sizeof(vis));
        int tt=Hungary(point);
        if(tt)
        {
            dfs(t+1);
            cov[tt]=0;
        }
    }
    point--;
}

int main()
{
    cin>>n>>m>>f;
    for(int i=1;i<=n;i++) 
        scanf("%d%d",&pos[i][0],&pos[i][1]);
    for(int i=1;i<=m;i++) 
        scanf("%d%d",&wea[i][0],&wea[i][1]);
    f*=f;
    for(int i=n;i>0;i--) 
        for(int j=m;j>0;j--)
            if(dist(pos[i][0]-wea[j][0],pos[i][1]-wea[j][1])<=f)
                bomb[j][i]=1;
    for(int i=1;i<=m;i++)
    {
        m[i][n+1]=n;
        for(int j=n;j>0;j--)
            if(bomb[i][j])
            {
                m[i][j]=m[i][j+1];
                len[j][m[i][j]]=1;
            }
            else m[i][j]=j-1;
    }
    for(int i=n;i>0;i--)
        for(j=i;j<=n;j++)
            if(len[i][j])
                map[i][++map[i][0]]=j;
    dis[n+1]=0;
    for(int i=n;i>0;i--)
        dis[i]=1+dis[map[i][map[i][0]]+1];

    dfs(1);
    cout<<res<<endl;
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值