题目链接:http://gdutcode.sinaapp.com/contest.php?cid=1056
Problem A: 两只老虎
正常的+有耳朵的 = a/2
正常的+有尾巴的 = b
正常的+有耳朵的+有尾巴的 = c/4
正常的 = a/2+b-c/4
#include
#include
#include
#include
using namespace std;
const int NUM = 1000;
void solve()
{
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
int ans = a/2+b-c/4;
printf("%d\n", ans);
}
int main()
{
int T;
scanf("%d", &T);
while(T--)
{
solve();
}
return 0;
}
Problem B: 占点游戏
令 d = abs(x1-x2)+abs(y1-y2)
首先判断(n+1)/2 >= d,先手可不可以从一个点走到另一个点 :
如果不可以,则先手可以多得 n&1 分(因为劣势者可以选择逃离)
如果可以,考虑 d 的奇偶性:
如果 d 为奇数(先手可以先踩到后手覆盖过的点):
如果 n 为奇数,先手可以多得 2 分,否则平。
否则(d 为偶数):
如果 n 为奇数,先手可以多得 1 分,否则后手可以多得 1 分。
#include
#include
using namespace std;
int main()
{
int T;
scanf("%d", &T);
while(T--)
{
int x1, y1, x2, y2, n;
scanf("%d%d%d%d%d", &n, &x1, &y1, &x2, &y2);
int tot = abs(x1-x2) + abs(y1-y2);
int ans = -1;
if((n+1)/2 >= tot)
{
if(tot&1)
{
if(n&1) ans = 2;
}
else ans = 1;
}
else if(n&1) ans = 1;
printf("%d\n", ans);
}
return 0;
}
Problem C: 爬楼梯
先分段考虑:
对于一段 x 阶的楼梯,方案数 f(x) = f(x-1)+f(x-2)+f(x-3) (其中 x >= 3),f(0)=1,f(1)=1, f(2) = 2。
那么对于爬 n 层楼,只需要算出每一段的方案数,然后使用乘法原理乘起来即可。
#include
#include
#include
#include
using namespace std;
const int mod = 10007;
const int N = 51;
int f[N];
void solve()
{
f[0] = 1;
for(int i = 1; i < N; ++ i)
{
f[i] = f[i-1];
if(i > 1) f[i] += f[i-2];
if(i > 2) f[i] += f[i-3];
f[i] %= mod;
}
int n;
scanf("%d", &n);
int ans = 1;
for(int i = 1; i < n; ++ i)
{
int x;
scanf("%d", &x);
ans = ans*f[x]%mod;
}
printf("%d\n", ans);
}
int main()
{
int T;
scanf("%d", &T);
while(T--)
{
solve();
}
return 0;
}
Problem D: 只有通过毁灭才能揭示真理
可以知道每 30 秒可以可以爆发一次 c 伤害,每 1 秒造成 b 伤害,那么答案=a*b+a/30*c。
#include
class Koz {
public:
int disintegration(int A, int B, int C) {
return A * B + A / 10 / 3 * C;
}
};
int main()
{
Koz koz;
int T;
scanf("%d", &T);
while( T-- ) {
int A, B, C;
std::cin >> A >> B >> C;
std::cout << koz.disintegration(A, B, C) << std::endl;
}
return 0;
}
Problem E: 倒水(Water)
对于 n 瓶一升的水,把他们合并后,最少需要的瓶子数为 n 的二进制中 1 的个数。假
设 n 的二进制中 1 的个数大于 k,那么我们要找到 1 个大于 n 的数,且二进制中 1 的个数等
于 k 的最小的数 m,那么答案为 m-n。
假设 n 二进制中,最右边的 1 在第 i 位(这里的第几位是从右往左数的,最右边为第 0
位),那么假设你加上一个小于 2^i 的数,结果二进制中 1 的个数只会增加,如果加上一个
2^i,则结果二进制中 1 的个数必定不会增加。所以只需要一直加上一个 2^i(这里的 i 表示
的是当前水的总体积的二进制中最右边的 1 所在的位置)直到结果中 1 的个数等于 k 即可。
#include
using namespace std;
int cal(int n)
{
int ans = 0;
while(n)
{
ans ++;
n &= n-1;
}
return ans;
}
int lowbit(int n)
{
return n&-n;
}
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
int n, k;
scanf("%d%d", &n, &k);
int ans = 0;
while(cal(n) > k)
{
ans += lowbit(n);
n += lowbit(n);
}
printf("%d\n", ans);
}
return 0;
}
Problem F: tmk 找三角
假设现在有 n 条线段,假设 n 条边从小到达排序,如果这 n 条边中没有三条可以构成
三角形,那么这 n 条边必须满足关系:A[i] >= A[i-2]+A[i-1],这里的 A[i]表示第 i 条边的大小。
假设 A[i]尽量取最小 A[i]=A[i-2]+A[i-1],且 A[1]=A[2]=1,是不是就是一个斐波那契,也就
是对于一个 n 条边的集合,如果不存在三条边能构成一个三角形,那么最长的边至少为 f[n],
表示斐波那契第 n 项。而题目中 A[i]<1e9,也就是只要 n>50,就必定存在三条边可以构成一
个三角形,所以我们只需要暴力加入两点路径上的边(如果大于 50,直接 Yes),然后对这
些边进行排序,枚举第 i 条边为最长边,贪心判断 A[i]是否小于 A[i-1]+A[i-2]即可。
#include
#include
#include
#include
#include
#include
using namespace std;
const int N = 1e5+10;
int n;
int tot, head[N], to[N<<1], nex[N<<1], len[N<<1];
int f[N], dis[N], dep[N];
void init()
{
tot = 0;
for(int i = 0; i <= n; ++ i)
{
head[i] = -1;
}
}
void addEdge(int x, int y, int l)
{
to[tot] = y;
len[tot] = l;
nex[tot] = head[x];
head[x] = tot++;
}
void dfs(int u, int d)
{
dep[u] = d;
for(int i = head[u]; ~i; i = nex[i])
{
int v = to[i];
if(v == f[u]) continue;
dis[v] = len[i];
f[v] = u;
dfs(v, d+1);
}
}
bool solve(int x, int y)
{
vector<int> vec;
while(vec.size() < 50 && x != y)
{
if(dep[x] < dep[y])
{
vec.push_back(dis[y]);
y = f[y];
}
else
{
vec.push_back(dis[x]);
x = f[x];
}
}
if(vec.size()>=50) return true;
sort(vec.begin(), vec.end());
for(int i = 0; i + 2 < vec.size(); ++ i)
{
if(vec[i] + vec[i+1] > vec[i+2]) return true;
}
return false;
}
int main()
{
int T;
scanf("%d", &T);
while(T--)
{
scanf("%d", &n);
init();
for(int i = 1; i < n; ++ i)
{
int x, y, l;
scanf("%d%d%d", &x, &y, &l);
addEdge(x, y, l);
addEdge(y, x, l);
}
dfs(1, 0);
int m;
scanf("%d", &m);
for(int i = 0; i < m; ++ i)
{
int x, y;
scanf("%d%d", &x, &y);
puts(solve(x, y) ? "Yes" : "No");
}
}
return 0;
}
Problem G: 等凹数字
dp[i][len][pre][up][down][ispa]代表当前第 i 位,长度为 len,上一位是什么,前面是否递
增过,前面是否递减过,当前是否符合回文串的性质,然后记忆化搜索
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
long long dp[20][20][10][2][2][2];
int num[20];
int s[20];
long long rec(int i,int pre,int up,int down,int flag,int q,int len,int ispa)
{
if(i<0)return up&&down&&ispa;
if(~dp[i][len][pre][up][down][ispa]&&!flag&&!q)return dp[i][len][pre][up][down][ispa];
long long res=0;
int o=s[i];
for(int j=0;j<10;j++)
{
num[i]=j;
if(j>o&&flag)break;
if(q)res+=rec(i-1,j,0,0,j0:flag,q&&j==0,len-(q&&j==0),ispa);
else if(j==pre)
{
if(ispa&&i2)
res+=rec(i-1,j,up,down,j0:flag,q&&j==0,len,j==num[len-i-1]);
else res+=rec(i-1,j,up,down,j0:flag,q&&j==0,len,ispa);
}
else if(j>pre)
{
if(!down)continue;
if(ispa&&i2)
res+=rec(i-1,j,1,down,j0:flag,q&&j==0,len,j==num[len-i-1]);
else res+=rec(i-1,j,1,down,j0:flag,q&&j==0,len,ispa);
}
else if(jif(up)continue;
if(ispa&&i2)
res+=rec(i-1,j,up,1,j0:flag,q&&j==0,len,j==num[len-i-1]);
else res+=rec(i-1,j,up,1,j0:flag,q&&j==0,len,ispa);
}
}
if(!flag&&!q)dp[i][len][pre][up][down][ispa]=res;
return res;
}
long long cal(long long x)
{
int len=0;
while(x)
{
s[len++]=x%10;
x/=10;
}
return rec(len-1,0,0,0,1,1,len,1);
}
int main()
{
memset(dp,-1,sizeof(dp));
long long l,r;
int t;
scanf("%d",&t);
while(t--){
scanf("%lld%lld",&l,&r);
printf("%lld\n",cal(r)-cal(l-1));
}
return 0;
}
Problem H: tmk 买礼物
首先,先对 a[i]从小到大排序,假设对于前 i 个硬币,我们可以组合成 0~y:
①如果 a[i+1]>y+1,那么从 i+1~n 中任意取硬币,构成的和都>y+1,所以必定构造不出
y+1,于是答案等于 y。
②如果 a[i+1]<=y+1,那么前 i+1 位可以组合成 0~y+a[i+1]。
所以只需要对硬币从小到大排序,然后从第一个硬币枚举到最后一个硬币,或者中途有
某个数够不出来即可得到答案。
#include
#include
#include
using namespace std;
typedef long long ll;
const int maxn = 1e5 + 5;
int a[maxn];
int main()
{
int T;
scanf("%d", &T);
while(T--)
{
int n;
scanf("%d", &n);
for(int i = 0; i < n; ++i) scanf("%d", &a[i]);
sort(a, a + n);
ll ans = 0;
for(int i = 0; i < n && ans + 1 >= a[i]; ++i) ans += a[i];
printf("%lld\n", ans);
}
return 0;
}