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=an−an−1 ,然后前面一段如果以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)−y≥0
,我们让前面的数尽量大,后面的数尽量小,于是前面取9999999999(11个9),后面取1e10,这样减过来还是负的,而
99∗10−109
是一个正数,所以不存在这样的11位数。
那么剩下10位数,我们考虑到如果把y拆成两段i和j,每段五位(i可以有前导0),即设 y=105∗i+j .
那么这个函数f其实可以这样拆: f(y,K)=f(i,k)+f(j,k) .
所以原式变形为: x=f(i,k)+105∗i+f(j,k)+j .
令 f1(i,k)=f(i,k)+105∗i,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满足等式即可,总的问题规模为 105∗log(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,即x的质因子有m个。设这m个因子为x1,x2,...xm,每个因子的幂为c1,c2,...cm。那么x=xc11xc22...xcmm,g(x)就表示将x分为两个数p和q,使得pq=x且gcd(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)|1≤pq≤n,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)|1≤p<q≤n,1≤pq≤n,gcd(p,q)=1}
.
由(2)式,
∑i=1ng(i)=2∗card{(p,q)|1≤p<q≤n,1≤pq≤n,gcd(p,q)=1}+1=2∗∑i=1n√[card{q|p<q≤np,gcd(p,q)=1}]+1=2∗∑i=1n√[card{q|1≤q≤np,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题完全没找到资料