HGOI20180831 NOIP2018模拟

input1:

4
4 4 4 4
3 2 4 5
4 5 5 5
1 7 3 2

output1:

Yes
Yes
Yes
No

好的吧数学题QwQ考场上没人做出来qwq

就是判断两个矩形能否互相放到对方里面

后来想了想这道题怎么那么简单,判断边长不就得了吗?

虽然想到这道题是T2但还是傻到交一个可能0分的程序上去。。。(详见说明)

于是就有这么多代码。。。

  • 大矩形的长大于小矩形的长,宽大于小矩形的宽,这时肯定可以放得下去;
  • 大矩形的对角线小于小矩形的对角线,那么也就没有地方容下小矩形了,这时判定否;

70pts code:

# include <bits/stdc++.h>
using namespace std;
int main()
{
    freopen("girls.in","r",stdin);
    freopen("girls.out","w",stdout);
    int n; scanf("%d",&n);
    for (int i=1;i<=n;i++) {
        int a,b,c,d;
        scanf("%d%d%d%d",&a,&b,&c,&d);
        if (a>b) swap(a,b);
        if (c>d) swap(c,d);
        if (a<=c&&b<=d) printf("Yes
");
        else if (c<=a&&d<=b) printf("Yes
");
        else printf("No
");
    }
    return 0;
}

然后70pts 

后来发现这是有反例的qwq?

斜着放?

像这样:

这样的话额。。好像小长方形略长一点好像是可以放下的qwq(其实也可以结合生活经验)

好的吧

 这个时候我们发现并不能直接下结论。。要推倒。。

  • 当小矩形的长大于大矩形的长时,此时斜放也可能放进大矩形,所以我们进行以下计算。

具体怎么算呢?

放个图:

思路:求出L1,L2验证以L1,L2构成直角三角形的斜边比y大那么就行否则不行。

BC2=x2+y2又 CI2=a2 所以 BI2=BC2-CI2=x2+y2-a2

所以BI=sqrt(x2+y2-a2

由△ABF≌DCH(显然AAS)

BF=CH=L1 而IG=CH(矩形)

所以在GF上有 BF+BI+IG=GF 代入得 2L1+BI=EH

所以 L1=(EH-BI)/2=(b-sqrt(x2+y2-a2))/2;

同理可知 L2=(GH-AJ)/2=(a-sqrt(x2+y2-b2))/2;

若Rt△CHD中有sqrt(CD)<sqrt(L12+L22)即 y2<L12+L22

则合法否则不合法。所以有这样一个程序:

  • 大矩形的长大于小矩形的长,宽大于小矩形的宽,这时肯定可以放得下去;
  • 大矩形的对角线小于小矩形的对角线,那么也就没有地方容下小矩形了,这时判定否;
  • 当小矩形的长大于大矩形的长时,此时斜放也可能放进大矩形,所以我们进行以下计算。如上图,假如左下角的那个小三角形,L1与L2求出第三条边大于等于小矩形的宽的话,那么小矩形就可以(碰壁)的放进去。

100pts code:

# include <bits/stdc++.h>
using namespace std;
bool fun(int x,int y,int a,int b)
//(x,y)能否放到(a,b),(长,宽)
{
    if (x<=a&&y<=b) return true;
    if (x*x+y*y>a*a+b*b) return false;
    double L1=(b-sqrt(x*x+y*y-a*a))/2.0;
    double L2=(a-sqrt(x*x+y*y-b*b))/2.0;
    if (L1*L1+L2*L2>=(double)y*y) return true;
    else return false;
}
int main()
{
    freopen("girls.in","r",stdin);
    freopen("girls.out","w",stdout);
    int T;scanf("%d",&T);
    while (T--) {
        int x,y,a,b;
        scanf("%d%d%d%d",&x,&y,&a,&b);
        if (x<y) swap(x,y);
        if (a<b) swap(a,b);
        if (fun(x,y,a,b)||fun(a,b,x,y)) printf("Yes
");
        else printf("No
");
    }
    return 0;
}

input1:

5 6
0 6 6 10 10 
2 0 7 8 6 
10 5 0 10 3 
9 5 8 0 7 
4 9 8 3 0 
1 2 3
1 4 1
2 1 3
1 4 2
1 1 2
2 4 1

output1:

6
11

题目意思:给出一幅完全图每次操作删一条边求两点最短路。(其中删边操作小于200)

要求任意两点最短路显然想到floyd最快!!!

但是要删掉一条边啊。不好办。

考场上第一想法是那道我们做过的O(n4)的一道加一条权为0的边求最短路

(好像是luogu2018新春模拟赛的T2)

想强制在线。。(这是显然想法好吧,是完全图、稠密图想到dijkstra好伐)

然后看到数据范围啦么小想一把暴力就过了吧,然后打了一个dijkstra堆优化好像是O(mn log n)

然后愉快的发现好像要T qwq

好的,这样以来就是想强制离线可不可以做,改删边为加边。

(反着做)我设现在要加的边为s-->t 权为w,

首先更新s--->t的最短路O(1)

那么枚举两个点u和v更新这两个点的最短距离显然有3种方法

  • u--->s--->t--->v
  • u--->t---->s--->v
  • u--->v

由于各个点最短路我是知道的了,那么这些求值就是O(1)的了加边更新复杂度为O(n2)

有不多于200个删边操作O(n3)显然是够的,查询的话就是O(1)

这就是离线的算法。

考场上打的n<=40000的暴力dijkstra没有删去将就着看看。

# include <bits/stdc++.h>
# define INF INT_MAX/3
# define Rint register int
using namespace std;
const int MAXN=205,MAXM=100005;
struct record{
    int pre,to,w;
}a[MAXN*MAXN];
int e[MAXN][MAXN],n,m,tot=0,head[MAXN];
struct qwq{
    int c,x,y;
}q[MAXM];
int rec[MAXN][MAXN],mp[MAXN][MAXN],ans[MAXM];
inline int read()
{
    int X=0,w=0; char c=0;
    while(c<'0'||c>'9') {w|=c=='-';c=getchar();}
    while(c>='0'&&c<='9') X=(X<<3)+(X<<1)+(c^48),c=getchar();
    return w?-X:X;
}
void adde(int u,int v,int w)
{
    a[++tot].pre=head[u];
    a[tot].to=v;
    a[tot].w=w;
    head[u]=tot;
}
struct ww{
    int id,lenth;
    bool operator <(const ww a) const{
        if (lenth!=a.lenth) return lenth>a.lenth;
        else return id>a.id;
    }
};
priority_queue<ww> qq;
int d[MAXN];
bool vis[MAXN];
inline void dijkstra(int s,int t)
{
    memset(vis,0,sizeof(vis));
    for (int i=1;i<=n;i++) d[i]=INF;
    d[s]=0;
    ww Node; Node.id=s;Node.lenth=0;qq.push(Node);
    while (!qq.empty()) {
        ww Node=qq.top();qq.pop();
        int u=Node.id;
        if (vis[u]) continue;
        vis[u]=true;
        for (int i=head[u];i;i=a[i].pre) {
            int v=a[i].to;
            if (d[v]-a[i].w>d[u]) {
                d[v]=d[u]+a[i].w;
                ww N;N.id=v;N.lenth=d[v];
                qq.push(N);
            }
        }
    }
    if (d[t]<INF) printf("%d
",d[t]);
    else printf("-1
");
}
void work1()
{
    int x,y,c;
    for (int i=1;i<=n;i++)
    for (int j=1;j<=n;j++)
    {
        scanf("%d",&x);
        if (i==j) continue;
        adde(i,j,x);e[i][j]=tot;
    }
    for (int i=1;i<=m;i++) {
        c=read();x=read();y=read();
        if (c==2) {
            dijkstra(x,y);
            continue;
        }
        a[e[x][y]].w=INF;
    }
}
int main()
{
    freopen("journey.in","r",stdin);
    freopen("journey.out","w",stdout);
    scanf("%d%d",&n,&m);
    if (m<=40000) {
        work1();
        return 0;
    }
    for (Rint i=1;i<=n;i++)
     for (Rint j=1;j<=n;j++)
      scanf("%d",&mp[i][j]),rec[i][j]=mp[i][j];
    for (Rint i=1;i<=m;i++) {
        q[i].c=read();q[i].x=read();q[i].y=read();
        if (q[i].c==1) mp[q[i].x][q[i].y]=INF;
    }
    for (Rint k=1;k<=n;k++)
     for (Rint i=1;i<=n;i++)
      for (Rint j=1;j<=n;j++)
        mp[i][j]=min(mp[i][j],mp[i][k]+mp[k][j]);
    for (Rint i=m;i>=1;i--) {
        if (q[i].c==2) {
              ans[++ans[0]]=mp[q[i].x][q[i].y];
              continue;
        }
        int s=q[i].x,t=q[i].y;
        mp[s][t]=min(mp[s][t],rec[s][t]);
        for (int u=1;u<=n;u++)
        for (int v=1;v<=n;v++)
         mp[u][v]=min(min(mp[u][v],mp[u][s]+mp[s][t]+mp[t][v]),mp[u][t]+mp[t][s]+mp[s][v]);
    }
    for (Rint i=ans[0];i>=1;i--)
     if (ans[i]>=INF) printf("-1");
     else printf("%d
",ans[i]);
    return 0;
}

input1:

5
1 2
2 3
3 4
4 5

output1:

1

input2:

8
1 2
1 3
2 4
2 5
3 6
3 7
1 8

output2:

2

题目意思:是在一棵树上找到两个点使各个点到这两点的最大距离最小。

是真的不会做。。

那么就暴力本来想打LCA的结果发现floyd更快。。

就是暴力枚举那两个点,再枚举各个点到这两个点最小距离求最大值(就是按照题意模拟吧)

# include <bits/stdc++.h>
using namespace std;
const int MAXN=2005;
int mp[MAXN][MAXN];
int n;
inline int read()
{
    int X=0,w=0; char c=0;
    while(c<'0'||c>'9') {w|=c=='-';c=getchar();}
    while(c>='0'&&c<='9') X=(X<<3)+(X<<1)+(c^48),c=getchar();
    return w?-X:X;
}
bool p[MAXN],vis[MAXN];
int main()
{
    freopen("ob.in","r",stdin);
    freopen("ob.out","w",stdout);
    scanf("%d",&n);
    for (int i=1;i<=n;i++)
     for (int j=1;j<=n;j++)
      if (i!=j) mp[i][j]=INT_MAX/3;
    for (int i=1;i<=n-1;i++) {
        int u,v;
        u=read();v=read();
        mp[u][v]=1; mp[v][u]=1;
    }
    for (int k=1;k<=n;k++)
     for (int i=1;i<=n;i++)
      for (int j=1;j<=n;j++)
       if (k!=i&&i!=j&&j!=k) mp[i][j]=min(mp[i][j],mp[i][k]+mp[k][j]);
    int ans=INT_MAX;
    for (int t1=1;t1<=n;t1++)
     for (int t2=1;t2<=n;t2++) {
        if (t1==t2) continue;
         int Max=0;
         for (int u=1;u<=n;u++) Max=max(Max,min(mp[u][t1],mp[u][t2]));
        ans=min(ans,Max);
     }
     printf("%d
",ans);
    return 0;
 }

话说考场上写了点奇怪的东西竟然没有T???

【话说如果做出来了就会update的】

  • 60pts Floyd暴力模拟!!!(就是上面那种方法)
  • 80pts(1) 期望树是随机的,那么默认树的直径是log级别很短,直接在直径上枚举两个点O(n log2n)-O(n3)
  • 80pts(2) 考虑两个奖杯管辖范围有边界枚举一条边作为边界(u---v)u作为一棵子树,v作为一棵子树,分别求两棵树的直径求最小值O(n2
  • 100pts

显然奖杯在树的直径上(奖杯首先要将树的直径覆盖),

所以二分答案MID,表示从一个点扩展开去可以覆盖的范围

然后check的时候判断这两点扩展开去染色是否存在没有被染色的点存在,

有就是false没有就是true

 其他方法:

  • 100pts (2)奖杯在直径上,二分答案后取离直径上离端点距离答案的点,遍历 check 一遍
  • 100pts (3)随便提一个节点为根,二分答案,深度最深的节点一定要被照顾到,所以最深的点往上跳 答案层即可,和其距离答案以内的点都删掉,再做一次。 此法可以拓展到 k 个奖杯。
  • 100pts (4)在 80 分的基础上用树形 dp,记下每个点向下前三长和向上一格后不回该子树最长的路径 长度。子树内直径是前两长的和与该子树各个子树直径取 max;子树外直径是父节点向上一格 后不回该子树最长的路径长度,前两长不进入该子树的向下最长路径这三条取前两长加起来与 父节点以上的答案取 max。
# include <bits/stdc++.h>
# define Rint register int
using namespace std;
const int Root=1,MAXN=200005;
struct rec{
    int pre,to;
}a[MAXN<<1];
bool col[MAXN];
int n,dep[MAXN],line[MAXN],head[MAXN],pre[MAXN],tot=0,ans;
void adde(int u,int v) //加边
{
    a[++tot].pre=head[u];
    a[tot].to=v;
    head[u]=tot;
}
int getpts() //求出最深点并且把dep赋值为0
{
    int Max=0,P=1;
    for (int i=1;i<=n;i++)
     if (Max<dep[i]) Max=dep[i],P=i;
    memset(dep,0,sizeof(dep));
    return P;
}
void getline(int u) //求树的直径到line[]里面方便计算
{
    memset(line,0,sizeof(line));
    while (pre[u]!=-1) {
        line[++line[0]]=u;
        u=pre[u];
    }
    line[++line[0]]=u;
}
void dfs1(int u,int fat,int depth) //树的直径先一边任一点开始dfs到最深点s
{
    dep[u]=depth;
    for (Rint i=head[u];i;i=a[i].pre){
        int v=a[i].to; if (v==fat) continue;
        dfs1(v,u,depth+1);
    }
}
void dfs2(int u,int fat,int depth) //从s开始dfs到最深点t并记录每个点是从谁(pre前驱)走过来的方便搞出line[]
{
    pre[u]=fat; dep[u]=depth;
    for (Rint i=head[u];i;i=a[i].pre){
        int v=a[i].to; if (v==fat) continue;
        dfs2(v,u,depth+1);
    }
}
void draw(int u,int step,int fat) //u向外染色step步
{
    col[u]=true; //col 是true表示染色 false没染色
    if (step==0) return;
    for (Rint i=head[u];i;i=a[i].pre){
        int v=a[i].to; if (v==fat) continue;
        draw(v,step-1,u);
    }
}
bool check(int MID) //判断是否可行
{
    memset(col,false,sizeof(col));
    int Node1=line[1+MID],Node2=line[line[0]-MID]; //最深处要顾及到
    draw(Node1,MID,-1);
    draw(Node2,MID,-1);
    for (Rint i=1;i<=n;i++)
     if (!col[i]) return false;
    return true;
}
void ErFen() //二分答案
{
    int l=1,r=line[0];
    while (l<=r){
        int mid=(l+r)>>1;
        if (check(mid)) r=mid-1,ans=mid;
        else l=mid+1;
    }
}
int main()
{
    scanf("%d",&n);
    if (n<=2) { printf("0
"); return 0; } //特判,不用走都能看到
    int u,v;
    for (Rint i=1;i<=n-1;i++)
     scanf("%d%d",&u,&v),adde(u,v),adde(v,u);
    dfs1(Root,-1,0); int s=getpts();
    memset(pre,0,sizeof(pre));
    dfs2(s,-1,0);    int t=getpts();
    getline(t);
    ErFen();
    printf("%d
",ans);
    return 0;
}
原文地址:https://www.cnblogs.com/ljc20020730/p/9565983.html