[18.8.20]校内NOIP模拟赛

T1 旋转多边形

题意

给你一个凸多边形,和多边形内一点$P$,求其滚动一圈,点$P$的轨迹长。

$0\leq n\leq50$

题解

**题。对于每个顶点求其与点$P​$的距离和两条边的夹角即可。

代码

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<cmath>
using namespace std;
template<typename T>
inline void read(T& s)
{
    s=0;int f=1;char c=getchar();
    while(c<'0' || c>'9'){if(c=='-')f=-1;c=getchar();}
    while(c>='0' && c<='9'){s=s*10+c-'0';c=getchar();}
    s*=f;
}
const double pi = acos(-1);
template <typename T>
T rabs(T x)
{
    return x>0?x:-x;
}
struct node
{
    int x,y;
    node(int a=0,int b=0)
    {
        x=a;
        y=b;
    }
    void readp()
    {
        read(x);
        read(y);
    }
    double the()
    {
        return atan2(double(x),double(y));
    }
    double dis()
    {
        return sqrt((double)(x*x+y*y));
    }
}s[100],xx;
node operator - (node a,node b)
{
    return node(a.x-b.x,a.y-b.y);
}
int n;
int main()
{
    read(n);
    for(int i=0;i<n;i++)
        s[i].readp();
    xx.readp();
    double ans=0;
    for(int i=0;i<n;i++)
    {
        int ne=(i+1)%n;
        int la=(i+n-1)%n;
        double th=(s[ne]-s[i]).the()-(s[la]-s[i]).the();
        while(th<0)  th+=pi;
        while(th>pi) th-=pi;
        th=pi-th;
        double r=(xx-s[i]).dis();
        ans+=th*r;
    }
    printf("%.3lf\n",ans);
    return 0;
}

T2 冒泡排序

题意

求冒泡排序外层循环执行$k$次后的序列。

$2\leq n \leq 10^5$。

题解

首先观察发现 冒泡排序每进行一次,每个数字最多向后移动一次,但向前移次数没有限制。

也就是说,最终序列的第$1$项一定是原序列的第$1$项到第$k+1$项里面其中一个。

显而易见,第$1$项一定是这里面最小的一个(如果存在一个比它更小的,那么$k$次操作后它一定在这个数字的前面,所以最小的一个一定是第$1$项)。

同理,第$2$项一定是剩余数字加上第$k+2$项内最小的一个(因为向前移动次数没有限制,所以需要继承上次的集合)。

因此,最终只需要维护一个大小始终为$k+1$的堆,然后每次取最小值即可。

代码

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<queue>
using namespace std;
template<typename T>
inline void read(T& s)
{
    s=0;int f=1;char c=getchar();
    while(c<'0' || c>'9'){if(c=='-')f=-1;c=getchar();}
    while(c>='0' && c<='9'){s=s*10+c-'0';c=getchar();}
    s*=f;
}
priority_queue<int>q;
int n,k;
int s[100005];
int ans[100005];
int tot=0;
int main()
{
    read(n);
    read(k);
    for(int i=0;i<n;i++)
        read(s[i]);
    if(k>=n)
    {
        sort(s,s+n);
        for(int i=0;i<n;i++)
            printf("%d ",s[i]);
        printf("\n");
        return 0;
    }
    for(int i=0;i<k;i++)
        q.push(-s[i]);
    for(int i=k;i<n;i++)
    {
        q.push(-s[i]);
        ans[tot++]=-q.top();
        q.pop();
    }
    while(!q.empty())
    {
        ans[tot++]=-q.top();
        q.pop();
    }
    for(int i=0;i<n;i++)
        printf("%d ",ans[i]);
    printf("\n");
    return 0;
}

T3 修建工厂

题意

给你一棵树,要求选$k$个叶子,使两两之间距离和最小。

$3\leq n \leq 10^5,0\leq k \leq 100$

题解

这题一眼树上背包。$dp_{i,j}$表示当前转移到了以$i$为根的子树,选了$j$个叶子的最小代价。

转移方法显而易见。

时间复杂度$O(nk)$

以下是(不够严谨的)证明。

显而易见,将选择叶子改为选择节点后,复杂度不变。

先考虑此问题的弱化版,限制不为$k$而是$n$。

对于$x$来讲,每一次和子节点$v$的子树合并,需要枚举$v$的子树大小和之前已经合并过的$x$的子树(以下称之为左侧子树)的大小。

显然,我们可以视为枚举左侧子树的每一个点以及$v$的子树的每一个点。两者的计算次数是相等的。

那么我们可以发现,任意两个点当且仅当在其最近公共祖先处被合并。

所以总复杂度为$O(n^2)$。

来看原问题。

如果需要合并后的子树大小没有超过$k$,和上面问题等价。

如果合并后的子树大小超过了$k$,那么选择的个数上限取$k$。

我们可以认为,个数上限为$k$枚举个数,与子树大小为$k$枚举子节点是等价的,显然这部分的上界是$O(nk)$。

所以当合并后子树超过$k$时,超过的部分会被删掉,设多余的部分为$x$。每次合并的计算次数大约是$kx$(不严谨,具体次数还和儿子的具体大小有关,均摊似乎是这个?),这部分是$O(nk)$的。

所以此问题解法复杂度为$O(nk)$。

代码

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<queue>
using namespace std;
template<typename T>
inline void read(T& s)
{
    s=0;int f=1;char c=getchar();
    while(c<'0' || c>'9'){if(c=='-')f=-1;c=getchar();}
    while(c>='0' && c<='9'){s=s*10+c-'0';c=getchar();}
    s*=f;
}
int box[100005],las[200005],edv[200005],edw[200005],cnt=0;
void adde(int u,int v,int w)
{
    las[++cnt]=box[u];
    box[u]=cnt;
    edv[cnt]=v;
    edw[cnt]=w;
}
int n,k;
int rot;
int dd[100005];
long long dp[100005][105];
int yzc[100005];
void dfs(int now,int fa)
{
    if(dd[now]==1)
    {
        dp[now][0]=dp[now][1]=0;
        yzc[now]=1;
        return;
    }
    dp[now][0]=0;
    for(int i=box[now];i;i=las[i])
        if(edv[i]!=fa)
        {
            int v=edv[i];
            int w=edw[i];
            dfs(v,now);
            for(int i=min(yzc[now],k);i>=0;i--)
                for(int j=1;j<=yzc[v] && i+j<=k;j++)
                    dp[now][i+j]=min(dp[now][i+j],dp[now][i]+dp[v][j]+1ll*j*(k-j)*w);
            yzc[now]+=yzc[v];
        }
}
int main()
{
    read(n);
    read(k);
    int a,b,c;
    memset(dp,23,sizeof(dp));
    for(int i=1;i<n;i++)
    {
        read(a);
        read(b);
        read(c);
        dd[a]++;
        dd[b]++;
        adde(a,b,c);
        adde(b,a,c);
    }
    for(int i=1;i<=n;i++)
        if(dd[i]!=1)
        {
            rot=i;
            break;
        }
    dfs(rot,0);
    printf("%lld\n",dp[rot][k]);
    return 0;
}
posted @ 2018-08-20 22:30  ranwen  阅读(332)  评论(0编辑  收藏  举报