题目地址C Stone Game
题目大意:n堆石子,每堆石子最多有3个,现在将这n堆石子合并,每次选择两堆来合并,合并两堆的花销为 ( x % 3 ) ( y % 3 ) (x\%3)(y\%3) (x%3)(y%3),求出最小花销
思路:一开始个数为3的是不用考虑的,直接考虑个数为2和个数为1即可,将2和1对应组合花销为2,判断剩余的是2还是1,再根据对应的情况遍历处理即可
AC代码:
#include
#define ll long long
#define INF 0x3f3f3f3f
using namespace std;
void solve()
{
ll a,b,c;
scanf("%lld%lld%lld",&a,&b,&c);
ll biao,base;
ll res=0;
if(a>b)
{
biao=a-b;
base=b;
res+=(base*2);
ll yu=biao%3;
biao/=3;
res+=(biao*3);
if(yu==2)
res++;
}
else
{
biao=b-a;
base=a;
res+=(base*2);
ll yu=biao%3;
biao/=3;
res+=(biao*6);
if(yu==2)
res+=4;
}
printf("%lld\n",res);
}
int main()
{
int t = 1;
while(t--)
{
solve();
}
return 0;
}
题目地址D Fight against involution
题目大意:期末考试,每个人试卷的字数都有一个范围 [ L i , R i ] [L_i,R_i] [Li,Ri],每个人的名次是班上所有字数小于等于他的字数的人的个数,问如何才能让人们写的总字数最少同时保证他的名次不低于所有人都写最多字数时的名次。
思路:将所有人的字数的范围按最大值从低到高排序,这样就能划分出一个名次的梯队,只要保证一个人的字数不低于他上一个梯队的字数就可以保证所有人的字数最少同时名次不降低。
AC代码:
#include
#define ll long long
#define INF 0x3f3f3f3f
using namespace std;
const int N=1e5+5;
pair<ll,ll> ran[N];
bool cmp(pair<ll,ll> p1,pair<ll,ll> p2)
{
if(p2.second > p1.second)
{
return true;
}
else if(p2.second == p1.second)
{
return p2.first > p1.first;
}
else
{
return false;
}
}
void solve()
{
int n;
scanf("%d",&n);
for(int i = 1;i <= n;i++)
{
scanf("%lld %lld",&ran[i].first,&ran[i].second);
}
sort(ran+1,ran+1+n,cmp);
ll res=0;
ll biao2=0;
for(int i=1;i<=n;)
{
ll rmax=ran[i].second;
ll biao=ran[i].first;
ll cnt=0;
while(i<=n)
{
if(ran[i].second!=rmax)
break;
cnt++;
biao=max(max(biao,ran[i].first),biao2);
i++;
}
res+=(cnt*biao);
biao2=biao;
}
printf("%lld\n",res);
}
int main()
{
int t = 1;
while(t--)
{
solve();
}
return 0;
}
题目地址G Xor Transformation
题目大意:给出两个正整数 X , Y ( X > Y ) X,Y(X>Y) X,Y(X>Y),每次操作X = X = X x o r A ( 0 ≤ A < X ) X=XxorA(0\le A \lt X) X=XxorA(0≤A<X),现在要操作至多5次使得 X X X变成 Y Y Y
思路:首先,KaTeX parse error: Double superscript at position 4: X^Y^̲X=Y,那么理论上进行两次操作即可,如果 X Y > X X^Y>X XY>X,那么先算 X Y X^Y XY即可,否则就直接把 X Y X^Y XY的结果与X异或即可
AC代码:
#include
#define ll long long
using namespace std;
int main() {
ll X,Y;
scanf("%lld%lld",&X,&Y);
ll xy=X^Y;
if(xy>X)
printf("2\n%lld %lld",Y,X);
else
printf("1\n%lld",xy);
return 0;
}
题目地址L Bit Sequence
题目大意:f(x)表示x的二进制下1的个数,现在有一个长为m的0,1序列a,问在区间[0,L]中有多少个x满足,对任意的0<=i<=m-1, f ( x + i ) m o d 2 = a i f(x+i)\mod2=a_i f(x+i)mod2=ai
思路:参考2020ICPC济南站L-Bit Sequence(数位dp)
题目条件比较繁杂,一步一步分析.
1.要求同时满足m个条件,那么递归出口就不是O(1)的判断,而是O(m)循环判断.
2.需要让我们判断的等式为 f ( x + i ) ≡ a i ( m o d 2 ) f(x+i) \equiv a_i (mod 2) f(x+i)≡ai(mod2)
2.1这个式子涉及到了加法,就可能有进位,比较难处理。但是发现i比较小,即只会对数L的低六位产生影响。所以可以暴力枚举低六位的情况。
2.2考虑来自低六位的进位会对【二进制中1】的个数的影响:
记录一个从第七位开始的连续的1的个数为one,sum是x从第7位开始所有1的个数。
如果进位f(x+i)就等于sum-one再加上x的后六位加上i生成数的1的个数
如果不进位f(x+i)就等于sum加上x+i的后六位1的个数。
所以若进位: f ( x + i ) ≡ s u m − o n e + f ( x & ( 1 < < 7 − 1 ) + i ) ( m o d 2 ) f(x + i) \equiv sum - one+f(x\&(1 << 7-1) + i) (mod \ 2) f(x+i)≡sum−one+f(x&(1<<7−1)+i)(mod 2)
不进位时: f ( x + i ) ≡ s u m + f ( ( x + i ) & ( 1 < < 7 − 1 ) ) ( m o d 2 ) f(x + i) \equiv sum +f((x + i)\&(1 << 7-1)) (mod \ 2) f(x+i)≡sum+f((x+i)&(1<<7−1))(mod 2)
( a + b + c ) m o d 2 ≡ ( a m o d 2 + b m o d 2 + c m o d 2 ) (a+b+c)\mod2 \equiv (a\mod2+b\mod2+c\mod2) (a+b+c)mod2≡(amod2+bmod2+cmod2)
然后我们知道一位二进制的加法减法都是异或。所以统一按异或处理即可.
所以总体上dp(前缀长度,数位和的奇偶,当前连续1的个数的奇偶,上界限制)
然后dfs到后六位时直接暴力统计即可.
注意点:
1.暴力统计的时候注意,若有上界限制,那么上界就应该是 L % 128 L\%128 L%128,否则是 2 7 − 1 2^7-1 27−1
2.还有记忆化搜索的时候注意由于递归出口不再是O(1)的复杂度,而是O(2^7*m),所以得优先记忆化返回,而不是递归出口在前。
时间复杂度:
复杂度分为两个部分,一个部分是记忆化搜索填表, O ( d p 数 组 大 小 ∗ 转 移 ) = O ( 16 ∗ log L ) O(dp数组大小*转移)=O(16*\log L) O(dp数组大小∗转移)=O(16∗logL)
一部分是后六位的暴力计算cal函数,根据状态个数,它最多被调用8次.每次复杂度为: O ( 2 7 ∗ m ) O(2^7*m) O(27∗m).总复杂度为: O ( 2 10 ∗ m ) O(2^{10}*m) O(210∗m)整个算法总复杂度为: O ( T ( 16 ∗ log L + 2 10 ∗ m ) ) O(T(16*\log L+2^{10}*m)) O(T(16∗logL+210∗m))
最差计算次数跑到1e8次左右,但是实际运行15ms。
AC代码:
#include
#define ll long long
using namespace std;
const int maxn = 100 + 5;
const int mod = 1e9 + 7;
int a[maxn] , m , dig[maxn] , cnt;
ll dp[65][2][2][2] , L;
int f[500];
ll cal (int sum , int one , int li)//这是对于低6位的计算
{
int s = (li ? L % 128 : 127);//如果有限制就是取L的二进制的后六位,否则就是到127
int ans = 0;
for (int i = 0 ; i <= s ; i++){
bool ok = true;
for (int j = 0 ; j < m && ok ; j++){
if (i + j < 128) ok = ((f[i + j] ^ sum) == a[j]);
else ok = ((f[i + j] ^ one ^ sum) == a[j]);
}
ans += ok;
}
return ans;
}
ll dfs (int step , int sum , int one , int li) {
//step当前位,sum数位和的奇偶,one从第7位开始连续的1的个数,li上界限制
ll &x = dp[step][sum][one][li];
if (~x) return x;
if (step <= 6){ //后六位单独计算
x = cal(sum , one , li);
return x;
}
int up = (li ? dig[step] : 1);
ll ans = 0;
for (int i = 0 ; i <= up ; i++){
int no = one;
if (i) no ^= 1;
else no = 0;
ans += dfs(step - 1 , sum ^ i , no , li && i == up);
}
return x = ans;
}
ll solve (ll x)
{
memset(dp , -1 , sizeof dp);
cnt = 0;
int len = 0;
for (int i = 63 ; i >= 0 ; i--){//下标从零开始
dig[i] = (x >> i) & 1;//取出x的二进制的每一位
if (dig[i]) len = max(len , i);//统计二进制下的长度
}
return dfs(len , 0 , 0 , 1);
}
int main()
{
ios::sync_with_stdio(false);
int t; cin >> t;
for (int i = 1 ; i < 500 ; i++)//一个数的二进制1的个数奇偶性等于它右移一位的数字的1的个数的奇偶性加上最后一位是不是1
f[i] = (f[i >> 1] + (i & 1))%2;
while (t--){
cin >> m; cin >> L;
for (int i = 0 ; i < m ; i++) cin >> a[i];
cout << solve (L) << endl;
}
return 0;
}
题目地址M Cook Pancakes!
题目大意:煎饼,一个面要煎一小时,只有一个锅,一个锅最多同时煎k个,一共有N个饼,问最少需要多少时间煎完N个饼。
思路:所有的饼都先煎正面再煎反面,2*N\k,如果有余数,结果再加一即可, 如果N小于等于K,一定是需要2小时。
AC代码:
#include
#define ll long long
#define INF 0x3f3f3f3f
using namespace std;
void solve()
{
int n, k;
scanf("%d%d",&n,&k);
if(n<=k)
{
printf("2\n");
}
else
{
if(2*n%k)
{
printf("%d\n",2*n/k+1);
}
else
{
printf("%d\n",2*n/k);
}
}
}
int main()
{
int t = 1;
while(t--)
{
solve();
}
return 0;
}