2019.09.28 多校联合训练(普及组)

采矿

一、题目

已知,矿场是一个平面直角坐标系;小周猪猪位于坐标(0, 0),即平面直角坐标系的原点。
已知有n个矿石,每一个矿石都一个坐标(x, y),其中x和y都是任意不为零的整数。当然,小周猪猪都能够准确的知道这些矿石的具体位置;它会依次按照矿石的编号,对这些矿石发射激光。
发射激光的规则是:
可以在所处的坐标原点上沿着某一个方向发射激光,这条激光上的每一个矿石都能被小周猪猪所有,且这些矿石被小周猪猪拿走以后便不复存在。
发射的激光是一条射线而不是一条直线,且这条射线是正比例函数(一定满足x > 0 或 x < 0).
现在,给你这 n 个点的坐标,小周猪猪会依次按照这 n 个点的顺序向其坐标所属方向发射激光。小周猪猪想要知道:每一次发射完激光完以后能够得到多少个矿石。

二、解法

[ a g c d , b g c d ] [\frac{a}{gcd},\frac{b}{gcd}] [gcda,gcdb]表示斜率,把它放进 m p mp mp中,注意判方向即可。
???

#include <cstdio>
#include <iostream>
#include <map>
using namespace std;
#define int long long
#define ull unsigned long long
const int MAXN = 100005;
int read()
{
    int x=0,flag=1;
    char c;
    while((c=getchar())<'0' || c>'9') if(c=='-') flag=-1;
    while(c>='0' && c<='9') x=(x<<3)+(x<<1)+(c^48),c=getchar();
    return x*flag;
}
int n,k,x[MAXN],y[MAXN];
map<ull,int> mp[4];
int abs(int x)
{
    return x>0?x:-x;
}
int gcd(int a,int b)
{
    return !b?a:gcd(b,a%b);
}
ull get_hash(int x,int y)
{
    return abs(x)*k+abs(y);
}
int locate(int x,int y)
{
    if(x>0 && y>0) return 0;
    if(x<0 && y>0) return 1;
    if(x<0 && y<0) return 2;
    return 3;
}
signed main()
{
    n=read();
    for(int i=1;i<=n;i++)
    {
        x[i]=read();y[i]=read();
        k=max(k,max(x[i],y[i]));
    }
    for(int i=1;i<=n;i++)
    {
        int d=gcd(abs(x[i]),abs(y[i]));
        mp[locate(x[i],y[i])][get_hash(x[i]/d,y[i]/d)]++;
    }
    for(int i=1;i<=n;i++)
    {
        int d=gcd(abs(x[i]),abs(y[i]));
        printf("%d\n",mp[locate(x[i],y[i])][get_hash(x[i]/d,y[i]/d)]);
        mp[locate(x[i],y[i])][get_hash(x[i]/d,y[i]/d)]=0;
    }
}


流浪月球

一、题目

在流浪月球期间,需要集齐n个魔法石才能开启传送门。在第i天,小周猪猪会得到数量在 [1, i] 范围内的传送石。当然,这一个数量小周猪猪可以随意选择。
在每一天,小周猪猪会将得到魔法石数量做一个标记,在返回地球的时候便会得到一个和为n的序列。
例如n = 4时,有 3 种序列:
(1, 1, 1, 1)
(1, 2, 1)
(1, 1, 2)
例如(1, 2, 1)表示第一天取了1个魔法石, 第二天取了2个魔法石,第三天取了 1个魔法石。
请问有多少个这样的序列,答案对10^9 + 7取模。

二、解法

考虑 d p dp dp,设 d p [ i ] [ j ] dp[i][j] dp[i][j]为第 i i i天拿到 j j j个宝石的方案数,则有:
d p [ i ] [ j ] = ∑ k = j − i j − 1 d p [ i − 1 ] [ k ] dp[i][j]=\sum_{k=j-i}^{j-1} dp[i-1][k] dp[i][j]=k=jij1dp[i1][k]
这样写就有 90 90 90分,在观察 d p dp dp式,发现可以用前缀和优化,时间复杂度 O ( 应 该 过 的 了 ) O(应该过的了) O()
???

#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
const int MOD = 1e9+7;
const int MAXN = 2005;
int read()
{
    int x=0,flag=1;
    char c;
    while((c=getchar())<'0' || c>'9') if(c=='-') flag=-1;
    while(c>='0' && c<='9') x=(x<<3)+(x<<1)+(c^48),c=getchar();
    return x*flag;
}
int n,cur,ans,dp[MAXN],sum[MAXN];
void mod(int &x)
{
    x=(x%MOD+MOD)%MOD;
}
int main()
{
    while(~scanf("%d",&n))
    {
        memset(sum,0,sizeof sum);
        sum[0]=1;ans=0;
        for(int i=1;i<=n;i++)
        {
            memset(dp,0,sizeof dp);
            for(int j=i;j<=min(i*(i+1)/2,n);j++)
            {
                if(j==i)
                    dp[j]=1;
                else
                    dp[j]=sum[j-1]-sum[j-i-1];
            }
            mod(ans+=dp[n]);
            memset(sum,0,sizeof sum);
            for(int j=i;j<=n;j++)
                mod(sum[j]=sum[j-1]+dp[j]);
        }
        printf("%d\n",ans);
    }
}


统计小猪猪

一、题目

已知小周猪猪家门口有一张1 * n的网格图,有m个关于小猪猪的信息需要统计。
每一个信息可以用一个四元组(ti, pi, di, vi)来表示。即第i头小猪在时间ti出现 ,在位置pi ,朝着方向di,以vi的速度运动着。di = 0表示朝向是左边, di = 1表示朝向是右边。
这些小猪猪运动到边界,即大于n或者小于1的位置,或者出现时间在统计时间以后的小猪猪,将跑出小周猪猪的视野范围,不会被小周猪猪统计。
现在有q个询问,每一个询问有一个数字 ,表示小周猪猪想要知道在时间Ti,有多少只小猪猪处于奇数点,有多少只小猪猪处于偶数点。由于猪猪国是一个和谐的国度,小猪猪相遇不会发生任何事情。

二、解法

这道题我们很用差分解决。
我们发现这些猪都有一个固定的出现时间和消失时间,那么这一段时间内的贡献+1。
如果 v v v是偶数,奇偶性不变,直接差分。
如果每一次的奇偶性是变化的,可能奇数时间的奇数答案+1 ,偶数时间的偶数答案+1。
当然存在另外的情况:奇数时间的偶数答案+1,偶数时间的奇数答案+1。
因此对于奇数和偶数交替的情况,维护每一个奇数位置在奇数时间/偶数时间的答案。同理,在偶数时间内维护奇数/偶数时间的答案。 .

#include <cstdio>
const int MAXN = 1000005;
int read()
{
    int x=0,flag=1;
    char c;
    while((c=getchar())<'0' || c>'9') if(c=='-') flag=-1;
    while(c>='0' && c<='9') x=(x<<3)+(x<<1)+(c^48),c=getchar();
    return x*flag;
}
int n,m,q,pre[2*MAXN][4];
int main()
{
	n=read();m=read();q=read();
	for(int i=1;i<=m;i++)
	{
		int t=read(),p=read(),d=read(),v=read(),e;
		if(d==0)
			e=t+(p-1)/v+1;
		else
			e=t+(n-p)/v+1;
		if(v%2==0)
			pre[t][p&1]++,pre[e][p&1]--;
		else
		{
			pre[t][((t&1)^(p&1))+2]++,pre[e][((t&1)^(p&1))+2]--;
		}
	}
	for(int i=1;i<=2*n;i++)
		for(int j=0;j<4;j++)
			pre[i][j]+=pre[i-1][j];
	while(q--)
	{
		int t=read();
		int ans1=pre[t][1],ans2=pre[t][0];
		if(t&1) ans1+=pre[t][2],ans2+=pre[t][3];
		else ans2+=pre[t][2],ans1+=pre[t][3];
		printf("%d %d\n",ans1,ans2);
	}
}

可爱路径

对于任意一个数,一定存在两种情况,即"大于中位数"和"小于中位数"两种。这样一来就具有单调性,我们会考虑枚举中位数,再通过判定就可以 log ⁡ V \log V logV的时间内得到中位数了。
考虑如何判定,判断一串数字的中位数是否大于 x x x,那么以为这一定有一半的数是大于 x x x的。
这个过程可以用 t p s o r t + d p tpsort+dp tpsort+dp实现,设 d p [ i ] [ j ] dp[i][j] dp[i][j]为跑到第 i i i个点经过 j j j个点,最多有多少个大于 x x x的点,则:
d p [ i ] [ j ] = { a [ i ] > = x ( j = 1 ) d p [ k ] [ j − 1 ] + a [ i ] > = x ( i , k 相 连 ) dp[i][j]=\begin{cases}a[i]>=x&(j=1)\\ dp[k][j-1]+a[i]>=x&(i,k相连)\end{cases} dp[i][j]={a[i]>=xdp[k][j1]+a[i]>=x(j=1)(i,k)

发现这个还是过不了链的数据,对于一条链,可以把它转化成区间问题,把大于 x x x的看作1,把小于 x x x的看作-1,发现只要满足 s i − s j ≥ 0 ∧ i − j ≥ k si-sj\geq 0\wedge i-j\geq k sisj0ijk,发现这个可以动态维护最小值(阉割版滑动窗口),就可以达到 O ( n ) O(n) O(n)检查了。

#include <cstdio>
#include <cstring>
#include <queue>
using namespace std;
const int inf = 0x3f3f3f3f;
const int MAXN = 1005;
int read()
{
    int x=0,flag=1;char c;
    while((c=getchar())<'0' || c>'9') if(c=='-') flag=-1;
    while(c>='0' && c<='9') x=(x<<3)+(x<<1)+(c^48),c=getchar();
    return x*flag;
}
int n,m,k,ans,tot,cnt,Min,Max,f[MAXN],in[MAXN],a[100005],tp[MAXN];
int dp[MAXN][MAXN],s[100005];
struct edge
{
    int v,next;
}e[3*MAXN];
void topol()
{
    queue<int> q;
    for(int i=1;i<=n;i++)
        if(in[i]==0)
            q.push(i);
    while(!q.empty())
    {
        int u=q.front();
        q.pop();
        tp[++cnt]=u;
        for(int i=f[u];i;i=e[i].next)
        {
            int v=e[i].v;
            in[v]--;
            if(!in[v])
                q.push(v);
        }
    }
}
bool check(int x)
{
    if(n>1000)
    {
        int t=0;
        for(int i=1;i<=n;i++)
            s[i]=s[i-1]+(a[i]>=x?1:-1);
        for(int i=k+1;i<=n;i++)
        {
            if(s[i]-t>=0) return 1;
            t=min(t,s[i-k]);
        }
        return 0;
    }
    memset(dp,0,sizeof dp);
    for(int i=1;i<=n;i++)
        dp[i][1]=(a[i]>=x);
    for(int l=1;l<n;l++)
    {
        for(int i=1;i<=n;i++)
        {
            int u=tp[i];
            for(int j=f[u];j;j=e[j].next)
            {
                int v=e[j].v;
                dp[v][l+1]=max(dp[v][l+1],dp[u][l]+(a[v]>=x));
            }
        }
    }
    for(int i=1;i<=n;i++)
        for(int j=k+1;j<=n;j++)
            if(dp[i][j]>=(j+1)/2)
                return 1;
    return 0;
}
void conquer(int l,int r)
{
    if(l>r) return ;
    int mid=(l+r)>>1;
    if(check(mid))
    {
        ans=mid;
        conquer(mid+1,r);
    }
    else
    {
        conquer(l,mid-1);
    }
}
int main()
{
    n=read();m=read();k=read();
    Min=inf,Max=ans=-inf;
    for(int i=1;i<=n;i++)
    {
        a[i]=read();
        Min=min(Min,a[i]);
        Max=max(Max,a[i]);
    }
    if(n<=1000)
    {
        for(int i=1;i<=m;i++)
        {
            int u=read(),v=read();
            e[++tot]=edge{v,f[u]},f[u]=tot;
            in[v]++;
        }
        topol();
    }
    conquer(Min,Max);
    if(ans==-inf)
        printf("No\n");
    else
        printf("%d\n",ans);
}
/*
7 8 3
46 79 97 33 22 1 122
1 2
1 5
2 3
2 6
3 4
6 4
5 7
4 7
*/

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值