hdu5933-5942 ccpc杭州站题解(7道题)

5933.ArcSoft’s Office Rearrangement(签到题)

http://acm.hdu.edu.cn/showproblem.php?pid=5933

题目大意:

Arcsoft公司有N个工作块排成一行,每个工作块里面有ai个人。现在要求把他们变成K个工作块,每个工作块里的人数相等,问至少操作几次?(如果不可能变成K个工作块,则输出-1)

每次操作可以把一个工作块拆成两个,也可以把两个工作块合并成一个。

题目分析:

首先呢,如果总人数不能被K整除,则输出-1,否则肯定有解。记总人数除以k=sum。

我们用一个栈来模拟,(有的题解用的是双端队列什么鬼),用一个数维护准备处理的人数,如果该人数 <sum <script type="math/tex" id="MathJax-Element-43">


#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define RE(x) freopen(x,"r",stdin)
#define WR(x) freopen(x,"w",stdout)
int T,n,k;
stack<ll> s;
ll a[100005];
ll sum;
int main() {
    //RE("in.txt");
    //WR("out.txt");
    scanf("%d",&T);
    for(int t=1;t<=T;t++) {
        scanf("%d %d",&n,&k);
        sum=0;
        for(int i=1;i<=n;i++) {
            scanf("%I64d",&a[i]);
            sum+=a[i];
        }
        if(sum%k) {
            printf("Case #%d: -1\n",t);
        }
        else {
            for(int i=n;i>=1;i--) {
                s.push(a[i]);
            }
            int ans=0;ll target=sum/k;
            ll cur=0;
            while(!s.empty()) {
                cur+=s.top();
                s.pop();
                if(cur==target) {
                    cur=0;
                }
                else if(cur<target) {
                    while(cur<target) {
                        cur+=s.top();
                        s.pop();
                        ans++;
                    }
                    if(cur==target) {
                        cur=0;
                    }
                    else {
                        if(cur%target) {
                            ans+=cur/target;
                            s.push(cur%target);
                        }
                        else {
                            ans+=cur/target-1;
                        }
                        cur=0;
                    }
                }
                else {
                    if(cur%target) {
                        ans+=cur/target;
                        s.push(cur%target);
                    }
                    else {
                        ans+=cur/target-1;
                    }
                    cur=0;
                }
            }
            printf("Case #%d: %I64d\n",t,ans);
        }
    }
}

5934.Bomb (Tarjan算法)

http://acm.hdu.edu.cn/showproblem.php?pid=5934

题目大意:

二维坐标系里有N个炸弹,每个炸弹的爆炸半径是r[i],引爆他的花费是c[i]。

若一个炸弹在另一个炸弹的爆炸半径内,那么引爆另一个炸弹的同时也会引爆它,是不需要花费的。问至少多少花费能引爆全部炸弹?

题目分析:

首先建图,如果炸弹i能引爆炸弹j,则加一条i->j的有向边。那么这张图有一个性质:如果引爆一个点,那么跟这个点处于同一强连通分量的其他点也都引爆了。那么对这个图缩点,缩成的点的权值取原来各点权值的最小值。这样得到一张新的dag图,取所有入度为0的点引爆即可。(因为如果入度不是0,那么可以通过引爆别的点将其引爆,一定不是最优的。)

#include <bits/stdc++.h>
using namespace std;
#define RE(x) freopen(x,"r",stdin)
#define WR(x) freopen(x,"w",stdout)
typedef long long ll;
int T,n;
ll x[1005],y[1005],r[1005],c[1005];
ll ans;
vector<int> g[1005];
int indegree[1005];
int low[1005],dfn[1005],stk[1005],belong[1005];
int num[1005];//每个强连通分量包含点个数
int index,top;
int scc;//强连通分量个数
bool instack[1005];
ll w[1005];//缩点后的最小值
void tarjan(int u) {
    low[u]=dfn[u]=++index;
    stk[top++]=u;
    instack[u]=true;
    int v;
    for(int i=0;i<g[u].size();i++) {
        v=g[u][i];
        if(!dfn[v]) {
            tarjan(v);
            if(low[u]>low[v])
                low[u]=low[v];
        }
        else if(instack[v] && low[u]>dfn[v])
            low[u]=dfn[v];
    }
    if(low[u]==dfn[u]) {
        scc++;
        v=stk[--top];
        instack[v]=false;
        belong[v]=scc;
        num[scc]++;
        while(v!=u) {
            v=stk[--top];
            instack[v]=false;
            belong[v]=scc;
            num[scc]++;
        }
    }
}
void solve() {
    memset(dfn,0,sizeof(dfn));
    memset(instack,0,sizeof(instack));
    memset(num,0,sizeof(num));
    memset(indegree,0,sizeof(indegree));
    index=top=scc=0;

    for(int i=1;i<=n;i++) {
        g[i].clear();
        for(int j=1;j<=n;j++) {
            if(j!=i) {
                ll dis = (x[i]-x[j])*(x[i]-x[j])+(y[i]-y[j])*(y[i]-y[j]);
                if(dis<=r[i]*r[i])
                    g[i].push_back(j);
            }
        }
    }

    for(int i=1;i<=n;i++) {
        if(!dfn[i])
            tarjan(i);
    }

    memset(w,0x3f,sizeof(w));
    for(int i=1;i<=n;i++) {
        int x=belong[i];
        w[x]=min(w[x],c[i]);
        for(int j=0;j<g[i].size();j++) {
            int u=g[i][j],y=belong[u];
            if(x!=y)
                indegree[y]++;
        }
    }
    ans=0;
    for(int i=1;i<=scc;i++) {
        if(indegree[i]==0)
            ans+=w[i];
    }
}
int main() {

    scanf("%d",&T);
    for(int t=1;t<=T;t++) {
        scanf("%d",&n);
        for(int i=1;i<=n;i++) {
            scanf("%I64d %I64d %I64d %I64d",&x[i],&y[i],&r[i],&c[i]);
        }
        solve();
        printf("Case #%d: %I64d\n", t,ans);
    }
}

5935.Car (贪心)

http://acm.hdu.edu.cn/showproblem.php?pid=5935

题目大意:

你在驾驶一辆车,然后有若干个计时点,经过每个计时点的时间必须是整数,从头到尾的速度是不减的,问至少需要多少时间?

题目分析:

一开始以为速度也必须是整数,题意理解错了好心塞TAT

既然速度允许是小数那么就很简单了,从后向前一段一段倒着分析,最后一段的时间必须是1,速度为 v=anan1 ,然后前面一段如果以v的速度走所需时间是整数,那么就继续以v的速度走(因为这样保证是最快的),否则时间向上取整。(因为此时速度肯定变少了),如果用浮点会存在错误,因为浮点数有可能是不准的,比如算出时间是6.0000001,其实是6,但是它就会向上取整。所以用分数表示速度(代码中用v1/v2表示)即可。

tips:其实本题用浮点数表示也可以,就是每次除完都要减1e-6修正一下。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int T,n;
ll a[100005];
#define RE(x) freopen(x,"r",stdin)
#define WR(x) freopen(x,"w",stdout)
int main() {
    scanf("%d",&T);
    for(int t=1;t<=T;t++) {
        scanf("%d",&n);
        for(int i=1;i<=n;i++) {
            scanf("%I64d",&a[i]);
        }
        ll ans=1;
        ll v1=a[n]-a[n-1],v2=1;
        for(int i=n-1;i>=1;i--) {
            ll d=a[i]-a[i-1];
            if((d*v2)%v1==0)
                ans+=d*v2/v1;
            else {
                ll t=(d*v2/v1)+1;
                ans+=t;
                v1=d;v2=t;
            }
        }
        printf("Case #%d: %I64d\n", t,ans);
    }
}

5936.Difference (枚举+二分)

http://acm.hdu.edu.cn/showproblem.php?pid=5935

题目大意:

f(y,K) 表示y的每个数位的k次方的和,给出x,k求满足 x=f(y,K)y 的y的个数。

题目分析:

题中x的数据范围最大到1e9,直接算肯定不可能,我们首先证明y不会超过1e10.
那么假设存在一个11位数的y,满足 f(y,k)y0 ,我们让前面的数尽量大,后面的数尽量小,于是前面取9999999999(11个9),后面取1e10,这样减过来还是负的,而 9910109 是一个正数,所以不存在这样的11位数。

那么剩下10位数,我们考虑到如果把y拆成两段i和j,每段五位(i可以有前导0),即设 y=105i+j .

那么这个函数f其实可以这样拆: f(y,K)=f(i,k)+f(j,k) .

所以原式变形为: x=f(i,k)+105i+f(j,k)+j .

f1(i,k)=f(i,k)+105i,f2(j,k)=f(j,k)+j .原式等价于寻找有序对 (i,j) 满足 x=f1(i,k)+f2(j,k) .而i和j的范围被下降到1e5以内。

接下来先预处理1e5内所有的 f1(i,k),f2(j,k) ,然后对f2排序。枚举i,二分查找有多少个j满足等式即可,总的问题规模为 105log(1e5) , 100个测试用例,在3s内显然可以解决。

这里有个小tips,我们在开数组的时候要开成f[k][i],这样方便对f2排序。还有,原题问的是多少个positive integer,而我们的i和j是从0~99999枚举的,所以要去掉i和j全为0的情况。。。(比如说水仙花数153)

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define RE(x) freopen(x,"r",stdin)
#define WR(x) freopen(x,"w",stdout)
int T,x,k;
ll f[10][100005],f1[10][100005],f2[10][100005];
void pre() {
    for(int k=0;k<=9;k++) {
        for(int i=0;i<1e5;i++) {
            if(i<10)
                f[k][i]=pow(i,k);
            else
                f[k][i]=f[k][i/10]+pow(i%10,k);
            f1[k][i]=f[k][i]-i*1e5;
            f2[k][i]=f[k][i]-i;
        }
    }
    for(int k=0;k<=9;k++) {
        sort(f2[k],f2[k]+100000);
    }
}
ll solve() {
    ll ans=0;
    for(int i=0;i<1e5;i++) {
        ll target=x-f1[k][i];
        int l=lower_bound(f2[k],f2[k]+100000,target)-f2[k];
        int r=upper_bound(f2[k],f2[k]+100000,target)-f2[k];
        ans+=r-l;
    }
    if(x==0)
        ans--;
    return ans;
}
int main() {

    pre();

    scanf("%d",&T);
    for(int t=1;t<=T;t++) {
        scanf("%d %d",&x,&k);
        ll ans=solve();
        printf("Case #%d: %I64d\n", t,ans);
    }

}

5938.Four Operations (贪心)

http://acm.hdu.edu.cn/showproblem.php?pid=5938

题目大意:

给一个长度为5~20的全1-9数字组成的字符串,在其中按序插入+ - * /,使得结果最大。

题目分析:

字符串中没有小数点和0,因此问题一下子简单了太多。
假设表达式为a+b-c*d/e,我们只需让a+b尽量大,c*d/e尽量小。随着位数的增加,a+b值和c*d/e值都是递增的,所以我们没法确定减号的位置,所以枚举减号的位置,分别取两头的最大值和最小值,相减,更新最大值即可。

很显然,减号前面那一段取1位数加多位数是最大的,有两种情况。

减号后面只需让e尽量大,所以c和d都取一位数,只有一种情况。

减号的位置要保证前面至少两位数,后面至少三位数,所以减号的位置至多只有16种情况,枚举之即可。因为总长度为20位,每一边的最大长度也只有18位,所以只需用long long,不需要大数模板。

#include <bits/stdc++.h>
using namespace std;
#define RE(x) freopen(x,"r",stdin)
#define WR(x) freopen(x,"w",stdout)
typedef long long ll;
int T,n;
char s[20];
ll myatol(int start,int end) {//[start,end)
    ll ans=0;
    for(int i=start;i<end;i++) {
        ans*=10;
        ans+=s[i]-'0';
    }
    return ans;
}
ll add1(int i) { //[0,i)
    ll x1=s[0]-'0',x2=myatol(1,i);
    ll y1=myatol(0,i-1),y2=s[i-1]-'0';
    return max(x1+x2,y1+y2);
}
ll add2(int i) { //[i,n)
    return (s[i]-'0')*(s[i+1]-'0')/myatol(i+2,n);
}
int main() {
    scanf("%d",&T);
    for(int t=1;t<=T;t++) {
        scanf("%s",s);
        n=strlen(s);
        ll ans=-1e18;
        for(int i=2;i<n-2;i++) {
            ll left=add1(i);//[0,i)的子串添加+号
            ll right=add2(i);//[i,n)子串添加* /号
            ans=max(ans,left-right);
        }
        printf("Case #%d: %I64d\n",t, ans);
    }
}

5942.Just a math problem(数学)

http://acm.hdu.edu.cn/showproblem.php?pid=5942

题目大意:

f(x) 表示x的质因子的个数, g(x)=2f(x) ,输入n,求 i=1ng(i) .n的范围是 1012 ,结果对 1e9+7 取模。

题目分析:

这道题确实对我来说有点难想,看了网上大多数题解也都是只言片语,个人比较渣,理解能力有限,看了很久才把这道题做出来。

其实关键点在于对这个 g(x) 以及那个和的理解。以下是推导过程(全用LaTex编辑算了):

f(x)=m,xmmx1,x2,...xm,c1,c2,...cmx=xc11xc22...xcmm,g(x)xpq使pq=xgcd(p,q)=1

大家可能有些不理解这个地方,这里解释一下。
因为 g(x)=2m=i=0mCim ,也就是从m个质因子中选择0个,1个…m个的方法数之和,而这个p就是把x的一部分因子全拿走,剩下的给q,组成p和q,这样的p和q肯定是互质的。

所以,

g(i)=card{(p,q)|pq=i,gcd(p,q)=1}(1)

因为对不同的i,有序数对 (p,q) 肯定不会有重复,因此原问题 i=1ng(i)=card{(p,q)|1pqn,gcd(p,q)=1}(2)
记里面那个集合为S.

接下来考虑 (p,q) ,因为 (p,q)S ,均有 (q,p)S ,所以只考虑 p<q 的情况, p>q 是对称的。而p=q就只有 (1,1) 一个解,因此下面只考虑 card{(p,q)|1p<qn,1pqn,gcd(p,q)=1} .
由(2)式,

i=1ng(i)=2card{(p,q)|1p<qn,1pqn,gcd(p,q)=1}+1=2i=1n[card{q|p<qnp,gcd(p,q)=1}]+1=2i=1n[card{q|1qnp,gcd(p,q)=1}φ(p)]+1 .

其中 φ(p) 为欧拉函数,表示不超过p的数里与p互质的数的个数。1e6内的可以套模板打表求之。

但是前面那段还是太大了,所以我们需要想办法求他。枚举p的质因子,记为集合 T={x1,x2,...,xm} ,和 xi 不互质的数的个数是 p/xi ,然后用容斥原理累加起来,分母是奇数个因子就减,偶数个就加(注意是跟容斥原理相反的,因为我们求的是互质的数,这样累加出来的恰好是不互质的数的相反数),加上n即可,这个部分是hdu4135的模板。因为p只有1e6个,所以可以记忆化。

所以其实这道题真正动键盘写的代码很少,就是两个模板的组合(欧拉函数预处理以及hdu4135题),真正有难度的是前面的数学推导,关键是对 2f(i) 这块的理解。

#include <bits/stdc++.h>
using namespace std;
#define RE(x) freopen(x,"r",stdin)
#define WR(x) freopen(x,"w",stdout)
typedef long long ll;
int T;ll n;
const int M = 1e9+7;
const int maxn = 1e6;

ll phi[maxn+5];
ll euler()
{
    int i,j;
    memset(phi,0,sizeof(phi));
    phi[1]=1;
    for(i=2;i<maxn;++i)//先打表出每个数的互质数个数
    {
        if(!phi[i])
        {
            for(j=i;j<maxn;j+=i)
            {
                if(!phi[j])
                    phi[j]=j;
                phi[j]=phi[j]/i*(i-1);
            }
        }
    }
}
int fac[1000005][20];
bool vis[maxn+5];
void Get_fac(int m)
{
    int m2=m;
    vis[m2]=true;
    int i,x=0;
    for(i=2;i*i<=m;i++)
        if(m%i==0){
            fac[m2][++x]=i;
            while(m%i==0)   m/=i;
        }
    if(m!=1) {
        fac[m2][++x]=m;

    }
    fac[m2][0]=x;
}

int sum[1<<11],q;
ll Sum(int m,ll n)
{
    int i,j;
    if(!vis[m])
        Get_fac(m);
    sum[0]=1;
    sum[1]=1;
    for(i=1;i<=fac[m][0];i++)
    {
        int k=sum[0];
        for(j=1;j<=sum[0];j++)
        {
            sum[++k]=fac[m][i]*sum[j]*-1;
        }
        sum[0]=k;
    }

    ll ret=n;
    for(i=2;i<=sum[0];i++)   ret+=n/sum[i];
    return ret;
}

ll solve() {
    ll ans=0;
    for(ll p=1;p*p<n;p++) {
        ans=(ans+Sum(p,n/p)-phi[p])%M;
    }
    return (ans*2+1)%M;
}
int main() {
    euler();
    memset(vis,0,sizeof(vis));
    scanf("%d",&T);
    for(int t=1;t<=T;t++) {
        scanf("%I64d",&n);
        printf("Case #%d: %I64d\n", t,solve());
    }
}

5943.Kingdom of Obsession(二分图匹配+数论)

http://acm.hdu.edu.cn/showproblem.php?pid=5942

题目大意:

输入n和s,有n个人,他们的编号分别是s+1,s+2,…s+n,现在有n个座位编号为1,2,…n,现在将这n个人安排到n个座位上,要求每个编号为x的人坐到编号为y的座位上,满足 x%y=0 .问是否存在这样的分配方法?

题目分析:

乍一看就是简单的二分图匹配,但是题中i和j的范围都达到了1e9,这么多点肯定没法匹配。
考虑到 [1,n] [s+1,s+n] 可能重复,而重叠的区间只需让他们坐在等于自己编号的座位即可。所以区间大小就缩小到了 min(s,n) .

可是这还是不够,这道题里有一个重要性质,也是解题的关键:若一个区间内有两个素数,那么必定是无解的。

用反证法证明,如果 编号i,j使得有解,那么i只能坐在1或i,又由上一步已经去掉了重叠区间,则他只能坐在1上,这样的话j就无解了,与有解矛盾。

根据度娘可知,1e13内最大的素数间隔只有777,所以问题又缩小到了777个点以内,这样就很好解决了。

#include <bits/stdc++.h>
using namespace std;
#define RE(x) freopen(x,"r",stdin)
#define WR(x) freopen(x,"w",stdout)
typedef long long ll;
int T,n,s;
vector<int> g[1600];
int f[1600],vis[1600];
bool dfs(int i) {
    vis[i]=1;
    for(int j=0;j<g[i].size();j++) {
        int u=g[i][j];
        if(f[u] == -1 || (!vis[f[u]] && dfs(f[u]))) {
            f[u]=i;
            f[i]=u;
            return 1;
        }
    }
    return 0;
}
int hungary(int num) {
    int cnt=0;
    memset(f,-1,sizeof(f));
    for(int i=1;i<=num*2;i++) {//总点数为2n或者2s
        if(f[i]==-1) {
            memset(vis,0,sizeof(vis));
            cnt+=dfs(i);
        }
    }
    return cnt;
}
int main() {
    RE("in.txt");
    WR("out.txt");
    scanf("%d",&T);
    for(int t=1;t<=T;t++) {
        scanf("%d %d",&n,&s);
        if(min(n,s)>777) {
            printf("Case #%d: No\n",t);
        }
        else {
            memset(g,0,sizeof(g));
            int num;
            if(n<s+1) {
                num=n;//2n个点
                for(int i=1;i<=n;i++) { //枚举位置
                    for(int j=1;j<=n;j++) { //枚举id,s+1~s+n
                        if((s+j)%i==0) {//加边
                            g[i].push_back(n+j);
                            g[n+j].push_back(i);
                        }
                    }
                }
            }
            else {
                num=s;//2s个点
                for(int i=1;i<=s;i++) { //枚举位置
                    for(int j=1;j<=s;j++) { //枚举id,n+1~s+n
                        if((n+j)%i==0) {
                            g[i].push_back(s+j);
                            g[s+j].push_back(i);
                        }
                    }
                }
            }
            int ans=hungary(num);
            if(ans==num) {
                printf("Case #%d: Yes\n",t);
            }
            else {
                printf("Case #%d: No\n",t);
            }
        }
    }
}

对着全网的题解也就做出来7道题,。。。E题,答案都没看懂~,GHI题完全没找到资料

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值