Who Gets the Most Candies?
题意:一群小朋友玩游戏,第i个出列的人将会得到f(i)颗糖,f(i)为i的所有因子,游戏指定了谁先开始,且接下来的人按出去的人的牌上的数字选出(约瑟夫环)
思路:预处理出所有的f(i),然后通过线段树在O(logn)内查询下一个人的位置,对于因子数量一样的情况,按题目要求取前面那个人。
#include
#include
#include
#define lson rt << 1,l,m
#define rson rt << 1|1,m+1,r
using namespace std;
typedef long long ll;
const int N = 5e5+10;
int sum[N << 2],f[N],card[N];
char name[N][12];
void init(){
memset(f,0,sizeof(f));
for(int i = 1;i*i <= N;++i){
for(int j = i+1;i*j <= N;++j) f[i*j] += 2;
f[i*i]++;
}
return ;
}
void pushup(int rt){
sum[rt] = sum[rt << 1]+sum[rt << 1|1];
return ;
}
void build(int rt,int l,int r){
if(l == r){
sum[rt] = 1;
return ;
}
int m = (l+r) >> 1;
build(lson);
build(rson);
pushup(rt);
return ;
}
inline int query(int pos,int rt,int l,int r){
if(l == r){
sum[rt]--;
return l;
}
int ans,m = (l+r) >> 1;
if(pos <= sum[rt << 1]) ans = query(pos,lson);
else ans = query(pos-sum[rt << 1],rson);
pushup(rt);
return ans;
}
int main()
{
init();
int n,k;
while(~scanf("%d%d",&n,&k)){
build(1,1,n);
for(int i = 1;i <= n;++i) scanf("%s%d",name[i],&card[i]);
int now,ans = -1,ansname,num = n;
for(int i = 1;i <= n;++i){
now = query(k,1,1,n);
num--;
if(f[i] > ans){
ans = f[i];
ansname = now;
}
if(!num) break;
if(card[now] > 0) k = (k-2+card[now])%num+1;
else k = ((k-1+card[now])%num+num)%num+1;
}
printf("%s %d\n",name[ansname],ans);
}
return 0;
}
Balanced Lineup
题意:问一个区间内,身高最高的奶牛和最低的奶牛差值为多少。
思路:线段树维护区间最值,每次查询query两次即可。
#include
#include
#include
#include
#include
using namespace std;
const int INF = 0x3f3f3f3f;
const int maxn = 5e4+10;
typedef long long ll;
int Max[maxn << 2],Min[maxn << 2];
void pushup(int rt){
Max[rt] = max(Max[rt << 1],Max[rt << 1|1]);
Min[rt] = min(Min[rt << 1],Min[rt << 1|1]);
return ;
}
void build(int rt,int l,int r){
if(l == r){
scanf("%d",&Max[rt]);
Min[rt] = Max[rt];
return ;
}
int m = (l+r) >> 1;
build(rt << 1,l,m);
build(rt << 1|1,m+1,r);
pushup(rt);
return ;
}
inline int queryMax(int rt,int L,int R,int l,int r){
if(L <= l && r <= R) return Max[rt];
int ans = 0;
int m = (l+r) >> 1;
if(L <= m) ans = max(ans,queryMax(rt << 1,L,R,l,m));
if(R > m) ans = max(ans,queryMax(rt << 1|1,L,R,m+1,r));
return ans;
}
inline int queryMin(int rt,int L,int R,int l,int r){
if(L <= l && r <= R) return Min[rt];
int ans = INF;
int m = (l+r) >> 1;
if(L <= m) ans = min(ans,queryMin(rt << 1,L,R,l,m));
if(R > m) ans = min(ans,queryMin(rt << 1|1,L,R,m+1,r));
return ans;
}
int main()
{
int n,q,l,r;
while(~scanf("%d%d",&n,&q)){
build(1,1,n);
while(q--){
scanf("%d%d",&l,&r);
printf("%d\n",queryMax(1,l,r,1,n) - queryMin(1,l,r,1,n));
}
}
return 0;
}
A Simple Problem with Integers
题意:区间修改和区间求和
思路:之前用线段树简单过了一次,然后看完蓝书之后发现树状数组的解法,大概理解为再开一个差分数组来实现区间修改。还没有完全理解透,但大概把代码码了出来。
#include
#include
using namespace std;
typedef long long ll;
const int maxn = 1e5+10;
ll a[maxn],sum1[maxn],sum2[maxn];
inline ll lowbit(int x){
return x&(-x);
}
void update(int pos,int val,int n){
ll k = pos;
for(;pos <= n;pos += lowbit(pos)){
sum1[pos] += val;
sum2[pos] += k*val;
}
return ;
}
inline ll getsum(int pos){
ll res = 0,k = pos+1;
for(;pos;pos -= lowbit(pos)) res += k*sum1[pos] - sum2[pos];
return res;
}
int main()
{
ll x,n,q,l,r,val;
while(~scanf("%lld%lld",&n,&q)){
a[0] = 0;
memset(sum2,0,sizeof(sum2));
memset(sum1,0,sizeof(sum1));
for(ll i = 1;i <= n;++i){
scanf("%lld",&x);
a[i] = a[i-1]+x;
}
while(q--){
char op[2];
scanf("%s",op);
if(op[0] == 'Q'){
scanf("%lld%lld",&l,&r);
printf("%lld\n",a[r]+getsum(r)-a[l-1]-getsum(l-1));
}
else{
scanf("%lld%lld%lld",&l,&r,&val);
update(l,val,n);
update(r+1,-val,n);
}
}
}
return 0;
}
Matrix
题意:矩阵区间修改和点查值。
思路:二维线段树,树上建树就完事了,因为这里实际上只有异或操作,所以我们可以利用lazy标记的思想,只标记lazy标记,在查询的过程中,看重复穿过lazy的标记标记有多少层,即可得到解。
#include
#include
#include
#define lson rt << 1,l,m
#define rson rt << 1|1,m+1,r
using namespace std;
typedef long long ll;
const int N = 1e3+5;
int tree[N << 2][N << 2];
int n,ans = 0;
void buildY(int rtX,int rt,int l,int r){
tree[rtX][rt] = 0;
if(l == r) return ;
int m = (l+r) >> 1;
buildY(rtX,lson);
buildY(rtX,rson);
return ;
}
void build(int rt,int l,int r){
buildY(rt,1,1,n);
if(l == r) return ;
int m = (l+r) >> 1;
build(lson);
build(rson);
return ;
}
void updateY(int rtX,int rt,int l,int r,int y1,int y2){
if(l >= y1 && r <= y2){
tree[rtX][rt] ^= 1;
return ;
}
int m = (l+r) >> 1;
if(y1 <= m) updateY(rtX,lson,y1,y2);
if(y2 > m) updateY(rtX,rson,y1,y2);
return ;
}
void update(int rt,int l,int r,int x1,int x2,int y1,int y2){
if(l >= x1 && r <= x2){
updateY(rt,1,1,n,y1,y2);
return ;
}
int m = (l+r) >> 1;
if(x1 <= m) update(lson,x1,x2,y1,y2);
if(x2 > m) update(rson,x1,x2,y1,y2);
return ;
}
void queryY(int rtX,int rt,int l,int r,int y1){
ans ^= tree[rtX][rt];
if(l == r) return ;
int m = (l+r) >> 1;
if(y1 <= m) queryY(rtX,lson,y1);
else queryY(rtX,rson,y1);
return ;
}
void query(int rt,int l,int r,int x1,int y1){
queryY(rt,1,1,n,y1);
if(l == r) return ;
int m = (l+r) >> 1;
if(x1 <= m) query(lson,x1,y1);
else query(rson,x1,y1);
return ;
}
int main()
{
int x1,x2,y1,y2,m,T;
scanf("%d",&T);
while(T--){
scanf("%d%d",&n,&m);
build(1,1,n);
char cmd;
while(m--){
getchar();
cmd = getchar();
if(cmd == 'C'){
scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
update(1,1,n,x1,x2,y1,y2);
}
else{
scanf("%d%d",&x1,&y1);
ans = 0;
query(1,1,n,x1,y1);
printf("%d\n",ans);
}
}
if(T) printf("\n");
}
return 0;
}
Help with Intervals
题意:建立一个集合操作系统,具体操作此处不描述。
思路:刚开始毫无思路。。。,没想出怎么用线段树模拟,后面看了一眼题解,发现题解里面给出了各个操作的模拟方式,就尝试写了一下。
U:把区间[l,r]覆盖成1
I:把[-∞,l)(r,∞]覆盖成0
D:把区间[l,r]覆盖成0
C:把[-∞,l)(r,∞]覆盖成0 , 且[l,r]区间0/1互换
S:[l,r]区间0/1互换
(摘自其他地方的题解)
因为此处有赋值和取反两个操作,姑且在维护一棵普通的涂色线段树的同时,再加一个lazy标记来记录取反,因为这次的线段树是有原值的,所以在下推时直接改变原值即可,无需像上一题那样在query中对每一层的lazy标记进行异或。
#include
#include
#include
#include
#define mid (l+r) >> 1
#define lson rt << 1,l,m
#define rson rt << 1|1,m+1,r
using namespace std;
const int N = 65536 << 1;
int tre[N << 2],laz[N << 2],rev[N << 2];
void build(int rt,int l,int r){
laz[rt] = -1;
rev[rt] = tre[rt] = 0;
if(l == r) return ;
int m = mid;
build(lson);
build(rson);
return ;
}
void pushdown(int rt){
if(laz[rt] != -1){
tre[rt << 1] = tre[rt << 1|1] = laz[rt << 1] = laz[rt << 1|1] = laz[rt];
rev[rt << 1] = rev[rt << 1|1] = 0;
laz[rt] = -1;
}
if(rev[rt]){
rev[rt << 1] ^= 1;
rev[rt << 1|1] ^= 1;
rev[rt] = 0;
}
return ;
}
void update(int rt,int l,int r,int L,int R,int val){
if(L > R) return ;
if(L <= l && r <= R){
if(val == 0 || val == 1){
tre[rt] = laz[rt] = val;
rev[rt] = 0;
}
else rev[rt] ^= 1;
return ;
}
pushdown(rt);
int m = mid;
if(L <= m) update(lson,L,R,val);
if(R > m) update(rson,L,R,val);
return ;
}
inline int query(int rt,int l,int r,int pos){
if(l == r){
if(rev[rt]){
tre[rt] ^= 1;
rev[rt] ^= 1;
}
return tre[rt];
}
pushdown(rt);
int m = mid;
if(pos <= m) return query(lson,pos);
else return query(rson,pos);
}
void put(int l,int r){
printf("%c%d,",(l&1) ?'(' :'[',l/2);
printf("%d%c",(r&1) ?(r+1)/2 :r/2,(r&1) ?')' :']');
return ;
}
void print(){
bool f = 0;
int sum = 0,t;
for(int i = 0;i < N;++i){
int isalive = query(1,0,N,i);
if(isalive && !f){
t = i;
f = 1;
}
else if(!isalive && f){
if(sum++) printf(" ");
put(t,i-1);
f = 0;
}
}
if(!sum) printf("empty set");
printf("\n");
return ;
}
int main()
{
char op,pre,pos;
int l,r;
build(1,0,N);
while(~scanf("%c%*c%c%d,%d%c%*c",&op,&pre,&l,&r,&pos)){
l = pre == '[' ?(l << 1) :(l << 1)+1;
r = pos == ']' ?(r << 1) :(r << 1)-1;
if(op == 'U') update(1,0,N,l,r,1);
else if(op == 'I'){
update(1,0,N,0,l-1,0);
update(1,0,N,r+1,N,0);
}
else if(op == 'D') update(1,0,N,l,r,0);
else if(op == 'C'){
update(1,0,N,0,l-1,0);
update(1,0,N,r+1,N,0);
update(1,0,N,l,r,-1);
}
else if(op == 'S') update(1,0,N,l,r,-1);
}
print();
return 0;
}
Can you answer these queries?
题意:对区间内所有的值求根,然后求区间和。
思路:刚开始感觉很无解,然后尝试每次的更新都更新到叶子节点,纯暴力写法。。。,然后tle了,最后看了网上题解,很巧妙的用区间和等于区间长度的操作实现了线段树上的剪枝?(剪枝这个词有点怪),确实很厉害。
#include
#include
#include
#define lson rt << 1,l,m
#define rson rt << 1|1,m+1,r
using namespace std;
typedef long long ll;
const int maxn = 1e5+10;
ll sum[maxn << 2];
void pushup(int rt){
sum[rt] = sum[rt << 1]+sum[rt << 1|1];
return ;
}
void build(int rt,int l,int r){
if(l == r){
scanf("%lld",&sum[rt]);
return ;
}
int m = (l+r) >> 1;
build(lson);
build(rson);
pushup(rt);
return ;
}
void update(int rt,int l,int r,int L,int R){
if(l == r){
sum[rt] = sqrt(sum[rt]);
return ;
}
int m = (l+r) >> 1;
if(L <= l && r <= R && sum[rt] == r-l+1) return ;
if(L <= m) update(lson,L,R);
if(R > m) update(rson,L,R);
pushup(rt);
return ;
}
inline ll query(int rt,int l,int r,int L,int R){
if(L <= l && r <= R) return sum[rt];
ll ans = 0;
int m = (l+r) >> 1;
if(L <= m) ans += query(lson,L,R);
if(R > m) ans += query(rson,L,R);
return ans;
}
int main()
{
int cmd,l,r,n,q,cas = 0;
while(~scanf("%d",&n)){
build(1,1,n);
scanf("%d",&q);
printf("Case #%d:\n",++cas);
while(q--){
scanf("%d%d%d",&cmd,&l,&r);
if(l > r) swap(l,r);
if(cmd) printf("%lld\n",query(1,1,n,l,r));
else update(1,1,n,l,r);
}
printf("\n");
}
return 0;
}
Atlantis
题意:求面积并。
思路:经典的扫描线问题,蓝书上面看见了才去做的。大部分都能理解,但对于左闭右开这个区间设定仍有异或。个人认为可能存在某种情况导致非重合区间重合,然后无法及时删去,但构思不出样例。以及个人认为,左闭右开的想法实际上是吧线段映射成了点,从而避免了所谓的重合。
#include
#include
#include
#include
#define mid (l+r) >> 1;
#define lson rt << 1,l,m
#define rson rt << 1|1,m+1,r
using namespace std;
typedef long long ll;
const int N = 1e2+5;
struct edg{
double y1,y2,x;
int val;
edg(){}
edg(double y1,double y2,double x,int val):y1(y1),y2(y2),x(x),val(val){}
bool operator < (const edg a){
return x < a.x;
}
} e[N << 1];
int s[N << 3];
double lsh[N << 1],sum[N << 3];
void pushup(int rt,int l,int r){
if(s[rt]) sum[rt] = lsh[r+1]-lsh[l];
else if(l == r) sum[rt] = 0;
else sum[rt] = sum[rt << 1]+sum[rt << 1|1];
return ;
}
void build(int rt,int l,int r){
sum[rt] = s[rt] = 0;
if(l == r) return ;
int m = mid;
build(lson);
build(rson);
return ;
}
void update(int rt,int l,int r,int L,int R,int val){
if(L <= l && r <= R){
s[rt] += val;
pushup(rt,l,r);
return ;
}
int m = mid;
if(L <= m) update(lson,L,R,val);
if(R > m) update(rson,L,R,val);
pushup(rt,l,r);
return ;
}
int main()
{
int tot,n,cas = 0;
double x1,x2,y1,y2;
while(scanf("%d",&n) && n){
tot = 0;
for(int i = 1;i <= n;++i){
scanf("%lf%lf%lf%lf",&x1,&y1,&x2,&y2);
edg t(y1,y2,x1,1);e[++tot] = t;lsh[tot] = y1;
edg p(y1,y2,x2,-1);e[++tot] = p;lsh[tot] = y2;
}
sort(e+1,e+tot+1);
sort(lsh+1,lsh+tot+1);
int cnt = unique(lsh+1,lsh+tot+1)-lsh-1;
build(1,1,cnt);
double ans = 0;
for(int i = 1;i < tot;++i){
int l = lower_bound(lsh+1,lsh+cnt+1,e[i].y1)-lsh;
int r = lower_bound(lsh+1,lsh+cnt+1,e[i].y2)-lsh-1;
update(1,1,cnt,l,r,e[i].val);
ans += (e[i+1].x-e[i].x)*sum[1];
}
printf("Test case #%d\n",++cas);
printf("Total explored area: %.2f\n\n",ans);
}
return 0;
}
Misunderstood … Missing Gym - 102056I
题意:你拥有两个属性A和D(初始为0),A为当前攻击力,D为每回合成长的攻击力,且在每个回合拥有三种选择,a[i]攻击并造成A+a[i]点伤害,使D增加b[i],使A增加c[i]。
思路:倒着dp,我们可以发现,对于三种可以转换为对伤害的贡献,操作一直接计算贡献即可,而操作二三则根据后面的攻击次数得到贡献,而攻击的次数和攻击的轮次可以是不定的,可以用区间覆盖的方式dp,做到拆分效果。
总结一下:
dp的三个因素:
1.第i回合
2.攻击j次
3.在第k个区间攻击
综上所述,三维dp,为了缩减空间,采用滚动数组优化即可。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
typedef long long ll;
const int N = 1e2+5;
ll a[N],b[N],c[N];
ll dp[2][N][5100];
int main() {
int n,T;
scanf("%d",&T);
while(T--){
scanf("%d",&n);
for(int i = 1;i <= n;++i) scanf("%lld%lld%lld",&a[i],&b[i],&c[i]);
memset(dp,0,sizeof(dp));
int now = 0;
dp[now^1][1][n] = a[n];
for(int i = n-1;i >= 1;--i){
for(int j = 1;j <= n-i;++j){
ll s = (i+i+j-1)*j/2,e = (n+n-j+1)*j/2;
for(int k = s;k <= e;++k){
dp[now][j+1][k+i] = max(dp[now][j+1][k+i],dp[now^1][j][k]+a[i]);
dp[now][j][k] = max(dp[now][j][k],dp[now^1][j][k]+j*c[i]);
dp[now][j][k] = max(dp[now][j][k],dp[now^1][j][k]+(k-i*j)*b[i]);
}
}
now ^= 1;
}
ll ans = 0;
for(int i = 1;i <= n;++i){
for(int j = 1;j <= 5100;++j){
ans = max(dp[now^1][i][j],ans);
}
}
printf("%lld\n",ans);
}
return 0;
}
Pastoral Life in Stardew Valley Gym - 102055G
题意:可以分一块放n*m的稻草人,但要保证稻草人被草包围,要对1e9+7取模。
思路:感觉是一个递推找规律的题目,推了一下3*n的小规模,忽然发现貌似是杨辉三角里的一条斜线C(3,n)+C(4,n),队友猜测矩形即是行贡献和列贡献的乘积,尝试写了一发交,wa在了精度了,改了点细节long long,成功ac。
#include
using namespace std;
typedef long long ll;
const ll MOD = 1e9+7;
inline qpow(ll a,ll b){
ll res = 1;
a %= MOD;
while(b){
if(b & 1) res = (res*a) % MOD;
a = (a*a) % MOD;
b >>= 1;
}
return res % MOD;
}
inline ll inv(ll x){
return qpow(x,MOD-2);
}
inline ll C(ll m,ll n){
if(m > n) return 0ll;
if(m == n || m == 0) return 1ll;
ll up = 1,down = 1;
for(ll i = n-m+1;i <= n;++i) up = (up*i) % MOD;
for(ll i = 1;i <= m;++i) down = (down*i) % MOD;
return (up*inv(down)) % MOD;
}
int main()
{
int T,cas = 0;
ll r,c;
scanf("%d",&T);
while(T--){
scanf("%lld%lld",&r,&c);
if(r < 3 || c < 3) printf("Case %d: 0\n",++cas);
else{
ll ans = (C(3,r)+C(4,r))%MOD;
ll ans2 = (C(3,c)+C(4,c))%MOD;
printf("Case %d: %lld\n",++cas,((ans*ans2)%MOD+MOD)%MOD);
}
}
return 0;
}
Mr. Panda and Kakin Gym - 102055K
题意:RAS解密,得到明文。。。没学过密码学,也没看懂题意,百度了一下RAS发现有现成的推的公式。。。
思路:公式已经出来了,但有个问题,数据规模太大,尝试__int 128,发现每个编译器都不支持,然后写了一个快速积,tle。。。看题解表示要用O(1)复杂度的快速积才行。。。
#include
using namespace std;
typedef long double ld;
typedef long long ll;
inline ll mul(ll a,ll b,ll c){return (a*b-(ll)((ld)a*b/c)*c+c)%c;}
inline ll qpow(ll a,ll b,ll n){
ll res = 1ll;
while(b){
if(b & 1) res = mul(res,a,n);
a = mul(a,a,n);
b >>= 1;
}
return res % n;
}
inline ll exgcd(ll a,ll b,ll &x,ll &y){
if(b == 0){
x = 1ll;
y = 0ll;
return a;
}
ll d = exgcd(b,a%b,y,x);
y -= a/b*x;
return d;
}
int main()
{
int T,cas = 0;
ll c,n,e,p,q,pq,d,y;
scanf("%d",&T);
while(T--){
scanf("%lld%lld",&n,&c);
e = (1 << 30)+3;
p = sqrt(n);
while(n % p != 0) p--;
q = n/p;
pq = (p-1)*(q-1);
exgcd(e,pq,d,y);
d = ((d%pq)+pq)%pq;
ll ans = qpow(c,d,n);
printf("Case %d: %lld\n",++cas,(ans+n)%n);
}
return 0;
}