ACM-ICPC 2018 南京赛区网络预赛(12/12)
A. An Olympian Math Problem
计算\(\sum_{i=1}^{n-1}i\cdot i!(MOD\ n)\)
\(\sum_{i=1}^{n-1}i\cdot i! = \sum_{i=1}^{n-1}[(i+1)!-i!](MOD\ n)=n!-1!(MOD\ n)=n-1\)
#include<bits/stdc++.h>
using namespace std;
int T; long long n;
int main(){
for(cin >> T; T; T--) cin >> n, cout << n - 1 << endl;
return 0;
}
B. The writing on the wall
考虑计算以每个点为右下角有多少个合法的矩形
可以发现符合条件的矩形可以用当前点和另一个代表左上角的点唯一表示出来,并且要求两个点构成的矩形中不存在黑点,那么现在对于每个右下角的点来说,方案数就是其合法左上角点的个数
这个可以用单调栈来做,先计算出每个点的向上最高高度
对于当前这个右下角点\(x\),找到其左边高度高于它的最后一个\(y\),假设高度分别为\(height_x,height_y\),合法左上角点为\(area_x,area_y\),可以得到这样的式子:\(area_x = area_y + (y-x+1)\cdot height_x\)
//#pragma GCC optimize("O3")
//#pragma comment(linker, "/STACK:1024000000,1024000000")
#include<bits/stdc++.h>
using namespace std;
function<void(void)> ____ = [](){ios_base::sync_with_stdio(false); cin.tie(0); cout.tie(0);};
const int MAXN = 1e5+7;
using LL = int_fast64_t;
int n,m,k,height[MAXN][111],area[111],lpos[111];
bool A[MAXN][111];
vector<pair<int,int> > vec;
void solve(int kase){
scanf("%d %d %d",&n,&m,&k);
vec.resize(k);
for(int i = 0; i < k; i++) scanf("%d %d",&vec[i].first, &vec[i].second);
for(auto p : vec) A[p.first][p.second] = true;
for(int j = 1; j <= m; j++) for(int i = 1; i <= n; i++) height[i][j] = (A[i][j]?0:height[i-1][j]+1);
LL ret = 0;
for(int i = 1; i <= n; i++){
stack<int> stk;
for(int j = 1; j <= m; j++){
area[j] = 0; lpos[j] = j;
while(!stk.empty() and height[i][stk.top()]>=height[i][j]){
lpos[j] = lpos[stk.top()];
stk.pop();
}
stk.push(j);
ret += area[j] = height[i][j] * (j - lpos[j] + 1) + area[lpos[j]-1];
}
}
printf("Case #%d: %lld\n",kase,ret);
for(auto p : vec) A[p.first][p.second] = false;
}
int main(){
int T; scanf("%d",&T);
for(int kase = 1; kase <= T; kase++) solve(kase);
return 0;
}
C. GDY
按题意模拟即可
//#pragma GCC optimize("O3")
//#pragma comment(linker, "/STACK:1024000000,1024000000")
#include<bits/stdc++.h>
using namespace std;
function<void(void)> ____ = [](){ios_base::sync_with_stdio(false); cin.tie(0); cout.tie(0);};
const int MAXN = 2e4+7;
class Player{
private:
int card[14],cardnum;
public:
explicit Player(){
memset(card,0,sizeof(card));
cardnum = 0;
}
void init(){
memset(card,0,sizeof(card));
cardnum = 0;
}
void Getcard(int ID){
//玩家拿卡
card[ID]++;
cardnum++;
}
bool empty(){ return cardnum==0; }
int put(int c){
if(c==-1){
for(int i = 3; i <= 13; i++){
if(card[i]){
card[i]--; cardnum--;
return i;
}
}
for(int i = 1; i <= 2; i++){
if(card[i]){
card[i]--; cardnum--;
return i;
}
}
}
if(card[c]){
card[c]--; cardnum--;
return c;
}
if(card[2]){
card[2]--; cardnum--;
return 2;
}
return -1;
}
int Calpenalties(){
int tot = 0;
for(int i = 1; i <= 13; i++) tot += i*card[i];
return tot;
}
}player[222];
class Game{
private:
int n,m,cur,skiptime,lastcard;
stack<int> stk;
int Needcard(){
if(lastcard==-1) return -1;
else if(lastcard>=3 and lastcard<=12) return lastcard+1;
else if(lastcard==13) return 1;
else if(lastcard==1) return 2;
else return 0;
}
int Getcardfrompile(){
//拿出下一张卡, 如果没卡返回-1
if(stk.empty()) return -1;
else{
int _ = stk.top();
stk.pop();
return _;
}
}
void Getinitcard(){
//轮流取牌
for(int i = 0; i < n; i++){
for(int j = 0; j < 5; j++){
int card = Getcardfrompile();
if(card==-1) return;
//没有卡了直接返回
player[i].Getcard(card);
}
}
}
void Nextplayer(int &now){ now = (now + 1) % n; }
void Playersgetcard(int now){
for(int i = now, flag = false; i!=now or !flag; Nextplayer(i)){
flag = true;
int card = Getcardfrompile();
if(card==-1) return;
player[i].Getcard(card);
}
}
void Taketurns(){
while(true){
int card = Needcard();
//需要的下一张打出的卡 如果当前是2 c返回0, 当前是-1 返回-1
bool done = false;
// done 判断这次是否出牌了
int putcard = player[cur].put(card);
// -1表示没有出卡牌 否则返回出了的牌
if(putcard!=-1){
done = true;
lastcard = putcard;
}
if(player[cur].empty()) return;
if(done){
if(lastcard!=2){
Nextplayer(cur);
skiptime = 1;
continue;
}
else skiptime = n;
}
else Nextplayer(cur), skiptime++;
if(skiptime==n){
Playersgetcard(cur);
lastcard = -1;
skiptime = 0;
}
}
}
void Showresault(){
for(int i = 0; i < n; i++){
int penalty = player[i].Calpenalties();
if(!penalty) puts("Winner");
else printf("%d\n",penalty);
}
}
public:
void init(){
//读入卡牌栈+初始化玩家
scanf("%d %d",&n,&m);
vector<int> vec(m);
for(int i = 0; i < m; i++) scanf("%d",&vec[i]);
while(!stk.empty()) stk.pop();
for(auto it = vec.rbegin(); it != vec.rend(); it++) stk.push(*it);
for(int i = 0; i < n; i++) player[i].init();
cur = 0; skiptime = 0; lastcard = -1;
}
void start(int kase){
Getinitcard();
Taketurns();
printf("Case #%d:\n",kase);
Showresault();
}
}game;
int main(){
int T; scanf("%d",&T);
for(int kase = 1; kase <= T; kase++){
game.init();
game.start(kase);
}
return 0;
}
D. Jerome's House
给出的价值其实就是三角形的面积的两倍
首先要把多边形所有边往内部缩进距离\(r\)
然后问题变成找出多边形内接最大三角形
可以枚举两个点然后三分第三个点来找最大值
//#pragma GCC optimize("O3")
//#pragma comment(linker, "/STACK:1024000000,1024000000")
#include<bits/stdc++.h>
using namespace std;
function<void(void)> ____ = [](){ios_base::sync_with_stdio(false); cin.tie(0); cout.tie(0);};
const double eps = 1e-8;
const double PI = acos(-1.);
const int MAXN = 1111;
int sgn( double ta, double tb){
if(fabs(ta-tb)<eps)return 0;
if(ta<tb) return -1;
return 1;
}
//点
class Point{
public:
double x, y;
Point(){}
Point( double tx, double ty){ x = tx, y = ty;}
bool operator < (const Point &_se) const{
return x<_se.x || (x==_se.x && y<_se.y);
}
friend Point operator + (const Point &_st,const Point &_se){
return Point(_st.x + _se.x, _st.y + _se.y);
}
friend Point operator - (const Point &_st,const Point &_se){
return Point(_st.x - _se.x, _st.y - _se.y);
}
double operator ^(const Point &b)const{
return x*b.y - y*b.x;
}
//点位置相同(double类型)
bool operator == (const Point &_off)const{
return sgn(x, _off.x) == 0 && sgn(y, _off.y) == 0;
}
};
/****************常用函数***************/
//点乘
double dot(const Point &po,const Point &ps,const Point &pe){
return (ps.x - po.x) * (pe.x - po.x) + (ps.y - po.y) * (pe.y - po.y);
}
//叉乘
double xmult(const Point &po,const Point &ps,const Point &pe){
return (ps.x - po.x) * (pe.y - po.y) - (pe.x - po.x) * (ps.y - po.y);
}
class Line{
public:
Point s, e;//两点表示,起点[s],终点[e]
double a, b, c;//一般式,ax+by+c=0
double angle;//向量的角度,[-pi,pi]
Line(){}
Line( Point ts, Point te):s(ts),e(te){}//get_angle();}
Line(double _a,double _b,double _c):a(_a),b(_b),c(_c){}
//排序用
bool operator < (const Line &ta)const{
if(angle!=ta.angle) return angle<ta.angle;
return ((s - ta.s)^(ta.e - ta.s)) < 0;
}
//向量与向量的叉乘
friend double operator / ( const Line &_st, const Line &_se){
return (_st.e.x - _st.s.x) * (_se.e.y - _se.s.y) - (_st.e.y - _st.s.y) * (_se.e.x - _se.s.x);
}
//向量间的点乘
friend double operator *( const Line &_st, const Line &_se){
return (_st.e.x - _st.s.x) * (_se.e.x - _se.s.x) - (_st.e.y - _st.s.y) * (_se.e.y - _se.s.y);
}
//从两点表示转换为一般表示
//a=y2-y1,b=x1-x2,c=x2*y1-x1*y2
bool pton(){
a = e.y - s.y;
b = s.x - e.x;
c = e.x * s.y - e.y * s.x;
return true;
}
//半平面交用
//点在向量左边(右边的小于号改成大于号即可,在对应直线上则加上=号)
friend bool operator < (const Point &_Off, const Line &_Ori){
return (_Ori.e.y - _Ori.s.y) * (_Off.x - _Ori.s.x)
< (_Off.y - _Ori.s.y) * (_Ori.e.x - _Ori.s.x);
}
//求直线或向量的角度
double get_angle( bool isVector = true){
angle = atan2( e.y - s.y, e.x - s.x);
if(!isVector && angle < 0)
angle += PI;
return angle;
}
//点在线段或直线上 1:点在直线上 2点在s,e所在矩形内
bool has(const Point &_Off, bool isSegment = false) const{
bool ff = sgn( xmult( s, e, _Off), 0) == 0;
if( !isSegment) return ff;
return ff
&& sgn(_Off.x - min(s.x, e.x), 0) >= 0 && sgn(_Off.x - max(s.x, e.x), 0) <= 0
&& sgn(_Off.y - min(s.y, e.y), 0) >= 0 && sgn(_Off.y - max(s.y, e.y), 0) <= 0;
}
//------------直线和直线(向量)-------------
//向量向左边平移t的距离
Line& moveLine( double t){
Point of;
of = Point( -( e.y - s.y), e.x - s.x);
double dis = sqrt( of.x * of.x + of.y * of.y);
of.x= of.x * t / dis, of.y = of.y * t / dis;
s = s + of, e = e + of;
return *this;
}
//直线重合
static bool equal(const Line &_st,const Line &_se){
return _st.has( _se.e) && _se.has( _st.s);
}
//直线平行
static bool parallel(const Line &_st,const Line &_se){
return sgn( _st / _se, 0) == 0;
}
//两直线(线段)交点
//返回-1代表平行,0代表重合,1代表相交
static bool crossLPt(const Line &_st,const Line &_se, Point &ret){
if(parallel(_st,_se)){
if(Line::equal(_st,_se)) return 0;
return -1;
}
ret = _st.s;
double t = ( Line(_st.s,_se.s) / _se) / ( _st / _se);
ret.x += (_st.e.x - _st.s.x) * t;
ret.y += (_st.e.y - _st.s.y) * t;
return 1;
}
};
class Polygon{
public:
const static int maxpn = 5e4+7;
Point pt[maxpn];//点(顺时针或逆时针)
Line dq[maxpn]; //求半平面交打开注释
int n;//点的个数
int judege( Line &_lx, Line &_ly, Line &_lz){
Point tmp;
Line::crossLPt(_lx,_ly,tmp);
return sgn(xmult(_lz.s,tmp,_lz.e),0);
}
int halfPanelCross(vector<Line> &L){
int i, tn, bot, top, ln = L.size();
for(int i = 0; i < ln; i++)
L[i].get_angle();
sort(L.begin(),L.end());
//平面在向量左边的筛选
for(i = tn = 1; i < ln; i ++)
if(fabs(L[i].angle - L[i - 1].angle) > eps)
L[tn ++] = L[i];
ln = tn, n = 0, bot = 0, top = 1;
dq[0] = L[0], dq[1] = L[1];
for(i = 2; i < ln; i ++){
while(bot < top && judege(dq[top],dq[top-1],L[i]) > 0)
top --;
while(bot < top && judege(dq[bot],dq[bot+1],L[i]) > 0)
bot ++;
dq[++ top] = L[i];
}
while(bot < top && judege(dq[top],dq[top-1],dq[bot]) > 0)
top --;
while(bot < top && judege(dq[bot],dq[bot+1],dq[top]) > 0)
bot ++;
dq[++top] = dq[bot];
for(i = bot; i < top; i ++)
Line::crossLPt(dq[i],dq[i + 1],pt[n++]);
return n;
}
};
//以上是板子
int n,r;
Polygon poly;
vector<Point> vec;
vector<Line> vecl;
double Area(Point &A, Point &B, Point &C){ return fabs(xmult(A,B,C)); }
double maxArea(int m){
double ret = 0;
for(int l = 0; l < m; l++){
for(int r = l + 1; r < m; r++){
Point A = vec[l], B = vec[r];
int L = l, R = r;
while(L<R-1){
int mid = (L+R) >> 1;
int rmid = (mid + R) >> 1;
if(Area(A,B,vec[mid])<Area(A,B,vec[rmid])) L = mid;
else R = rmid;
}
ret = max(ret,max(Area(A,B,vec[L]),Area(A,B,vec[R])));
}
}
return ret;
}
void solve(){
scanf("%d %d",&n,&r);
vec.resize(n);
for(int i = 0; i < n; i++){
int x,y; scanf("%d %d",&x,&y);
vec[i].x = double(x); vec[i].y = double(y);
}
reverse(vec.begin(),vec.end());
vecl.clear();
for(int i = 0; i < n; i++){
Point A = vec[i];
Point B = vec[(i+1)%n];
Line L = Line(A,B);
vecl.push_back(L.moveLine(r));
}
int nd = poly.halfPanelCross(vecl);
vec.resize(2*nd);
for(int i = 0; i < nd; i++) vec[i] = vec[i+nd] = poly.pt[i];
printf("%.6f\n",maxArea(nd));
}
int main(){
int T;
for(scanf("%d",&T); T; T--) solve();
return 0;
}
E. AC Challenge
状压DP
//#pragma GCC optimize("O3")
//#pragma comment(linker, "/STACK:1024000000,1024000000")
#include<bits/stdc++.h>
using namespace std;
function<void(void)> ____ = [](){ios_base::sync_with_stdio(false); cin.tie(0); cout.tie(0);};
using LL = int_fast64_t;
LL f[1<<20];
const LL INF = 0x3f3f3f3f3f3f3f3f;
int premsk[20],n;
pair<LL,LL> parameter[20];
int main(){
____();
cin >> n;
for(int i = 0; i < n; i++){
cin >> parameter[i].first >> parameter[i].second;
int num; cin >> num;
while(num--){
int pre; cin >> pre;
pre--; premsk[i] |= (1<<pre);
}
}
memset(f,-0x3f,sizeof(f));
f[0] = 0;
LL ret = 0;
for(int msk = 0; msk < (1<<n); msk++){
if(f[msk]==-INF) continue;
for(int i = 0; i < n; i++){
if(msk&(1<<i) or (msk&premsk[i])!=premsk[i]) continue;
f[msk|(1<<i)] = max(f[msk|(1<<i)],f[msk]+parameter[i].second+parameter[i].first*(__builtin_popcount(msk)+1));
ret = max(ret,f[msk|(1<<i)]);
}
}
cout << ret << endl;
return 0;
}
F. An Easy Problem On The Trees
LCT维护字树大小
大佬题解:蒙特卡洛可以发现询问就是求 (sz[u]-1)/d[u]*2,其中 sz[u] 是联通块大小,d[u] 是度数。然后就是一个很经典的维护子树大小的 lct 了。
//#pragma GCC optimize("O3")
//#pragma comment(linker, "/STACK:1024000000,1024000000")
#include<bits/stdc++.h>
using namespace std;
function<void(void)> ____ = [](){ios_base::sync_with_stdio(false); cin.tie(0); cout.tie(0);};
const int MAXN = 1e5+7;
typedef long long int LL;
const LL MOD = 998244353;
int n,m;
LL qpow(LL a, LL b){
LL ret = 1;
while(b){
if(b&1) ret = ret * a % MOD;
b >>= 1;
a = a * a % MOD;
}
return ret;
}
class Link_cut_tree{
private:
int fa[MAXN],ch[MAXN][2],sz[MAXN],deg[MAXN],rev[MAXN],img[MAXN];
bool isroot(int rt){ return ch[fa[rt]][0]!=rt and ch[fa[rt]][1]!=rt; }
int check(int rt){ return rt == ch[fa[rt]][1]; }
void pushdown(int rt){
if(!rev[rt]) return;
rev[ch[rt][0]] ^= 1;
rev[ch[rt][1]] ^= 1;
swap(ch[rt][0],ch[rt][1]);
rev[rt] ^= 1;
}
void pushdownall(int rt){
if(!isroot(rt)) pushdownall(fa[rt]);
pushdown(rt);
}
void pushup(int rt){ sz[rt] = sz[ch[rt][0]] + sz[ch[rt][1]] + img[rt] + 1; }
void rotate(int rt){
int f = fa[rt], gf = fa[f], d = check(rt);
if(!isroot(f)) ch[gf][check(f)] = rt;
fa[rt] = gf;
ch[f][d] = ch[rt][d^1]; fa[ch[rt][d^1]] = f;
ch[rt][d^1] = f; fa[f] = rt;
pushup(f); pushup(rt);
}
void splay(int rt){
pushdownall(rt);
while(!isroot(rt)){
int f = fa[rt];
if(!isroot(f)){
if(check(rt)==check(f)) rotate(f);
else rotate(rt);
}
rotate(rt);
}
}
public:
explicit Link_cut_tree(){ for(int i = 1; i < MAXN; i++) sz[i] = 1; }
void access(int x){
int c = 0;
while(x){
splay(x);
img[x] += sz[ch[x][1]] - sz[c];
ch[x][1] = c;
pushup(x);
x = fa[c = x];
}
}
void makeroot(int x){
access(x);
splay(x);
rev[x] ^= 1;
}
int findroot(int x){
access(x);
splay(x);
while(ch[x][0]) x = ch[x][0];
splay(x);
return x;
}
bool link(int x, int y){
if(findroot(x)==findroot(y)) return false;
makeroot(x);
makeroot(y);
deg[x]++; deg[y]++;
fa[y] = x;
img[x] += sz[y];
return true;
}
bool cut(int x, int y){
if(findroot(y)!=findroot(x) or x==y) return false;
makeroot(x);
access(y);
splay(y);
pushdown(y);
int rt = ch[y][0];
while(true){
pushdown(rt);
if(ch[rt][1]) rt = ch[rt][1];
else break;
}
splay(rt);
deg[rt]--; deg[y]--;
ch[rt][1] = fa[y] = 0;
pushup(rt);
return true;
}
LL query(int u){
makeroot(u);
pushdown(u);
return 2ll * (sz[u]-1) * qpow(deg[u],MOD-2) % MOD;
}
}lct;
int main(){
scanf("%d %d",&n,&m);
for(int i = 1; i < n; i++){
int u, v;
scanf("%d %d",&u,&v);
lct.link(u,v);
}
while(m--){
int op; scanf("%d",&op);
if(op==1){
int u, v;
scanf("%d %d",&u,&v);
if(!lct.link(u,v)) puts("-1");
}
else if(op==2){
int u, v;
scanf("%d %d",&u,&v);
if(!lct.cut(u,v)) puts("-1");
}
else if(op==3){
int u; scanf("%d",&u);
printf("%lld\n",lct.query(u));
}
}
return 0;
}
G. Lpl and Energy-saving Lamps
线段树
先把每个可以换灯的月份算出来
然后每次询问二分找这个月份就好了
//#pragma GCC optimize("O3")
//#pragma comment(linker, "/STACK:1024000000,1024000000")
#include<bits/stdc++.h>
using namespace std;
function<void(void)> ____ = [](){ios_base::sync_with_stdio(false); cin.tie(0); cout.tie(0);};
const int MAXN = 1e5+7;
const int INF= 0x3f3f3f3f;
int n,m,A[MAXN];
vector<pair<int,pair<int,int>>> vec;
class SegmentTree{
private:
int l[MAXN<<2], r[MAXN<<2], minn[MAXN<<2];
#define ls(rt) rt << 1
#define rs(rt) rt << 1 | 1
#define pushup(rt) minn[rt] = min(minn[ls(rt)],minn[rs(rt)])
public:
void build(int L, int R, int rt = 1){
l[rt] = L; r[rt] = R;
if(l[rt]+1==R){
minn[rt] = A[L];
return;
}
int mid = (L+R) >> 1;
build(L,mid,ls(rt)); build(mid,R,rs(rt));
pushup(rt);
}
void update(int pos ,int x, int rt = 1){
if(l[rt]+1==r[rt]){
minn[rt] = x;
return;
}
int mid = (l[rt] + r[rt]) >> 1;
if(pos<mid) update(pos,x,ls(rt));
else update(pos,x,rs(rt));
pushup(rt);
}
int qmin(){ return minn[1]; }
pair<int,int> findlmin(int x, int rt = 1){
if(l[rt]+1==r[rt]) return make_pair(l[rt],minn[rt]);
if(minn[ls(rt)]<=x) return findlmin(x,ls(rt));
else return findlmin(x,rs(rt));
}
}ST;
void solve(){
int mth; scanf("%d",&mth);
int p = lower_bound(vec.begin(),vec.end(),make_pair(mth,make_pair(INF,INF))) - vec.begin() - 1;
if(p==(int)vec.size()-1){
printf("%d %d\n",vec[p].second.first,vec[p].second.second);
return;
}
printf("%d %d\n",vec[p].second.first,vec[p].second.second+(mth-vec[p].first)*m);
}
int main(){
scanf("%d %d",&n,&m);
for(int i = 1; i <= n; i++) scanf("%d",A+i);
ST.build(1,n+1);
vec.push_back(make_pair(0,make_pair(0,0)));
while(ST.qmin()!=INF){
int minn = ST.qmin();
int month = (minn-vec.back().second.second) / m + ((minn-vec.back().second.second)%m==0?0:1);
int tot = vec.back().second.second + month * m;
int num = 0;
while(ST.qmin()<=tot){
auto p = ST.findlmin(tot);
int pos = p.first, val = p.second;
tot -= val;
ST.update(pos,INF);
num++;
}
vec.push_back(make_pair(vec.back().first+month,make_pair(vec.back().second.first+num,tot)));
}
int q; scanf("%d",&q);
while(q--) solve();
return 0;
}
H. Set
01字典树,合并
考虑第一个操作,可以用并查集来做
第二个操作其实就是在字典树中交换左右儿子,然后不断向\(0\)子树递归,这个可以用\(lazy\)标记来优化一下
第三个操作其实就是01字典树从根向下\(k\)的深度每一位和\(x\)相同的数量,统计一下就好了
有点卡常
//#pragma GCC optimize("O3")
//#pragma comment(linker, "/STACK:1024000000,1024000000")
#include<bits/stdc++.h>
using namespace std;
function<void(void)> ____ = [](){ios_base::sync_with_stdio(false); cin.tie(0); cout.tie(0);};
const int MAXN = 6e5+7;
int n,m,root[MAXN],tot,sum[MAXN<<5],ch[MAXN<<5][2],lazy[MAXN<<5];
int findx(int x){ return x==root[x]?x:root[x]=findx(root[x]); }
#define swap(u, v) u^=v^=u^=v;
inline void pushdown(int rt){
if(lazy[rt]&1){
swap(ch[rt][0],ch[rt][1]);
lazy[ch[rt][0]]++;
}
lazy[ch[rt][0]] += lazy[rt] >> 1;
lazy[ch[rt][1]] += lazy[rt] >> 1;
lazy[rt] = 0;
}
inline void insert(int rt, int x){
for(int i = 0; i < 30; i++){
sum[rt]++;
int bit = (x & (1<<i))?1:0;
if(!ch[rt][bit]) ch[rt][bit] = ++tot;
rt = ch[rt][bit];
}
sum[rt]++;
}
int merge(int u, int v){
if(!u or !v) return u^v;
if(lazy[u]) pushdown(u);
if(lazy[v]) pushdown(v);
sum[u] += sum[v];
ch[u][0] = merge(ch[u][0],ch[v][0]);
ch[u][1] = merge(ch[u][1],ch[v][1]);
return u;
}
inline int query(int rt, int x, int k){
for(int i = 0; i < k; i++){
if(lazy[rt]) pushdown(rt);
int bit = (x&(1<<i))?1:0;
if(!ch[rt][bit]) return 0;
rt = ch[rt][bit];
}
return sum[rt];
}
inline int read(){
int x = 0, f = 1;
char c = getchar();
while(c!='-'&&(c<'0'||c>'9')) c = getchar();
if(c=='-') f = -1,c = getchar();
while(c>='0'&&c<='9') x = x*10+c-'0', c = getchar();
return f*x;
}
int main(){
n = read(); m = read();
for(int i = 1; i <= n; i++) root[i] = i;
tot = n;
for(int i = 1; i <= n; i++){
int x = read();
insert(i,x);
}
while(m--){
int op = read();
if(op==1){
int u = read(), v = read();
int fu = findx(u), fv = findx(v);
if(fu==fv) continue;
root[fv] = merge(fu,fv);
}
else if(op==2){
int u = read();
lazy[findx(u)]++;
}
else if(op==3){
int u = read(), k = read(), x = read();
printf("%d\n",query(findx(u),x,k));
}
}
return 0;
}
I. Skr
有个结论,本质不同的回文子串数量不会超过字符串长度
Solution1: 可以先马拉车跑出来,然后对于每个回文中点从最长的子串开始找,直到这个回文串出现过停止
计算总和可以用类似哈希的前缀和
直接用\(set\)判重会T
这里用拉链法来处理冲突
//#pragma GCC optimize("O3")
//#pragma comment(linker, "/STACK:1024000000,1024000000")
#include<bits/stdc++.h>
using namespace std;
function<void(void)> ____ = [](){ios_base::sync_with_stdio(false); cin.tie(0); cout.tie(0);};
const int MAXN = 2e6+7;
typedef long long int LL;
const int base = 231LL;
const int MOD = 1e9+7;
const int hashmod = 5e6+7;
char s[MAXN],str[MAXN<<1];
int n,m,len[MAXN<<1],head[hashmod],nxt[MAXN],w[MAXN],tot;
int hax[MAXN],powbase[MAXN];
int pre[MAXN],powt[MAXN];
void manacher(){
int st = 1;
len[0] = 0; len[1] = 1;
for(int i = 2; i < m; i++){
int lp = 2 * st - i;
if(st+len[st]>i+len[lp]) len[i] = len[lp];
else{
int j = st + len[st] - i;
if(j<0) j = 0;
while(i-j>=0 and i+j<m and str[i-j]==str[i+j]) j++;
len[i] = j - 1;
st = i;
}
}
}
void preprocess(){
hax[0] = s[0]; powbase[0] = 1;
powt[0] = 1; pre[0] = s[0] - '0';
for(int i = 1; i < n; i++){
hax[i] = (1ll * base * hax[i-1] + s[i]) % hashmod;
powbase[i] = 1ll * powbase[i-1] * base % hashmod;
pre[i] = (pre[i-1] * 10ll + s[i] - '0') % MOD;
powt[i] = powt[i-1] * 10ll % MOD;
}
}
int calhash(int L, int R){
if(!L) return hax[R];
int tp = (hax[R] - 1ll * hax[L-1] * powbase[R-L+1]) % hashmod + hashmod;
return tp>=hashmod?tp-hashmod:tp;
}
int calval(int L, int R){
if(!L) return pre[R];
int tp = (pre[R] - 1ll * pre[L-1] * powt[R-L+1]) % MOD + MOD;
return tp>=MOD?tp-MOD:tp;
}
bool exist(int val, int L, int R){
int hashval = calhash(L,R);
for(int i = head[hashval]; i; i = nxt[i]) if(w[i]==val) return true;
tot++;
w[tot] = val; nxt[tot] = head[hashval];
head[hashval] = tot;
return false;
}
int main(){
scanf("%s",s);
n = strlen(s);
m = 0;
for(int i = 0; i < n; i++){
str[m++] = '#';
str[m++] = s[i];
}
str[m++] = '#'; str[m] = '\0';
manacher();
preprocess();
int ret = 0;
for(int i = 0; i < m; i++){
int lp, rp;
if(i&1) lp = i / 2 - len[i] / 2, rp = i / 2 + len[i] / 2;
else lp = i / 2 - len[i] / 2, rp = i / 2 - 1 + len[i] / 2;
while(lp<=rp){
int val = calval(lp,rp);
if(exist(val,lp,rp)) break;
ret = (ret + val) % MOD;
lp++, rp--;
}
}
printf("%d\n",ret);
return 0;
}
Solution2: 当然也能用PAM来做,而且跑得飞快,因为PAM的每个节点上都表示一个不同的回文,所以只要记下这个回文出现的一个位置和长度就可以了
//#pragma GCC optimize("O3")
//#pragma comment(linker, "/STACK:1024000000,1024000000")
#include<bits/stdc++.h>
using namespace std;
function<void(void)> ____ = [](){ios_base::sync_with_stdio(false); cin.tie(0); cout.tie(0);};
const int MAXN = 2e6+7;
const int MOD = 1e9+7;
int pref[MAXN],powt[MAXN],n;
char s[MAXN];
int calval(int L, int R){ return (0ll + pref[R] + MOD - 1ll * pref[L-1] * powt[R-L+1] % MOD) % MOD; }
class PAM{
private:
int tot,last,ch[MAXN][10],fail[MAXN],len[MAXN],rpos[MAXN];
public:
PAM(){
len[0] = 0; len[1] = -1;
fail[0] = 1; fail[1] = 0;
tot = 1;
}
int getfail(int x, int pos){
while(s[pos]!=s[pos-len[x]-1]) x = fail[x];
return x;
}
void insert(int pos){
int c = s[pos] - '0';
int u = getfail(last,pos);
if(!ch[u][c]){
len[++tot] = len[u] + 2;
fail[tot] = ch[getfail(fail[u],pos)][c];
rpos[tot] = pos;
ch[u][c] = tot;
}
last = ch[u][c];
}
int solve(){
int ret = 0;
for(int i = 2; i <= tot; i++) ret = (ret + calval(rpos[i]-len[i]+1,rpos[i])) % MOD;
return ret;
}
}pam;
int main(){
scanf("%s",s+1);
n = strlen(s+1);
powt[0] = 1;
for(int i = 1; i <= n; i++){
powt[i] = (powt[i-1] * 10ll) % MOD;
pref[i] = (pref[i-1] * 10ll + s[i] - '0') % MOD;
pam.insert(i);
}
printf("%d\n",pam.solve());
return 0;
}
J. Sum
\(f(x)\)的计算方法如下:
把\(x\)分解质因数,如果某个素因子出现了三次及以上那么\(f(x)=0\),否则\(f(x)\)就等于\(2^{(只出现一次的素因子数量)}\),这个可以用欧拉筛来处理出来,然后求前缀和,每次查询复杂度为\(O(1)\)
//#pragma GCC optimize("O3")
//#pragma comment(linker, "/STACK:1024000000,1024000000")
#include<bits/stdc++.h>
using namespace std;
function<void(void)> ____ = [](){ios_base::sync_with_stdio(false); cin.tie(0); cout.tie(0);};
const int MAXN = 2e7+7;
using LL = int_fast64_t;
bool npm[MAXN];
int base1[MAXN],pw[MAXN];
LL sum[MAXN];
vector<int> prime;
void preprocess(){
pw[1] = 0;
for(int i = 2; i < MAXN; i++){
if(!npm[i]) prime.push_back(i), pw[i] = 1;
for(int j = 0; ; j++){
if(i*prime[j]>=MAXN) break;
npm[i*prime[j]] = true;
if(base1[i]<2) pw[i*prime[j]] = pw[i] + 1, base1[i*prime[j]] = 0;
else pw[i*prime[j]] = -1, base1[i*prime[j]] = 2;
if(i%prime[j]==0){
if(base1[i]) pw[i*prime[j]] = -1, base1[i*prime[j]] = 2;
else base1[i*prime[j]]++, pw[i*prime[j]] -= 2;
break;
}
}
}
for(int i = 1; i < MAXN; i++) sum[i] = sum[i-1] + (pw[i]==-1?0:(1ll<<pw[i]));
}
int main(){
int T,n;
preprocess();
for(cin >> T; T; T--) cin >> n, cout << sum[n] << endl;
return 0;
}
K. The Great Nim Game
Solution1: 线性基
Nim游戏中如果先手要获胜必须是所有石堆数量异或和不为\(0\)
现在问题转化为给出\(n\)堆石子,要求找到异或和不为\(0\)的子集数
可以先算异或和为\(0\)的子集数,然后再用总的去减掉
因为\(k\le 2^{12}\),所以出现的不同的数的数量不会超过\(4096\)
我们把每个数表示成二进制,然后取他们的一个线性基,假设这个线性基的秩是\(r\),答案就是\(2^n-2^{n-r}\),
可以这样想,因为这\(r\)个基底可以表示出所有\(n\)个数能表示出来的数,所以如果任意取不是基底的数,总能取出几个基底使总的异或和为\(0\),所以方案数就是非基底的数的任意组合方案数
//#pragma GCC optimize("O3")
//#pragma comment(linker, "/STACK:1024000000,1024000000")
#include<bits/stdc++.h>
using namespace std;
function<void(void)> ____ = [](){ios_base::sync_with_stdio(false); cin.tie(0); cout.tie(0);};
using LL = int_fast64_t;
const LL MOD = 1e9+7;
const int MAXN = 1e7+7;
char n[MAXN];
LL x1,a,b,c,d,e;
int k,base[13],len;
LL qpow(LL x, LL y){
LL ret = 1;
while(y){
if(y&1) ret = ret * x % MOD;
y >>= 1;
x = x * x % MOD;
}
return ret;
}
int f(LL x){ return (a*x*x*x*x+b*x*x*x+c*x*x+d*x+e-1) % k + 1; }
int main(){
scanf("%s %lld %lld %lld %lld %lld %lld %d",n,&x1,&a,&b,&c,&d,&e,&k);
int num = k;
len = strlen(n);
if(len<6){
int tp = 0;
for(int i = 0; i < len ; i++) tp = tp * 10 + n[i] - '0';
num = min(num,tp);
}
set<int> S;
int Rank = 0;
for(int i = 1; i <= num; i++, x1 = f(x1)){
if(S.count(x1)) break;
S.insert(x1);
int msk = x1;
for(int i = 12; i >= 0; i--){
if(!(msk&(1<<i))) continue;
if(!base[i]){
base[i] = msk;
Rank++;
break;
}
msk = msk ^ base[i];
}
}
LL pw = 0;
for(int i = 0; i < len; i++) pw = (pw * 10 + n[i] - '0') % (MOD - 1);
LL pn = qpow(2,pw);
printf("%lld\n",(pn-pn*qpow(qpow(2,Rank),MOD-2)%MOD+MOD)%MOD);
return 0;
}
Solution2: DP
\(DP[i][j]\)表示选到第\(i\)个数,异或和为\(j\)的方案数
而每次转移只考虑第\(i\)个数取了奇数次还是偶数次,转移方程是\(DP[i][j] = DP[i-1][j XOR A_i] + DP[i-1][j]\)
最后答案就是\((\sum_{i=1}^{k}dp[tot][i])\cdot 2^{n-k}\)
最后的\(2^{n-k}\)是因为每个数出现不止一次,之前\(DP\)的时候每次只考虑了这个数选和不选,现在我们如果先固定选了几次,如果固定次数是奇数,那么我现在再去选它,相当于不选(因为偶数次选择的异或和为0)所以剩下\(n-k\)个重复出现的数可以任意使用
//#pragma GCC optimize("O3")
//#pragma comment(linker, "/STACK:1024000000,1024000000")
#include<bits/stdc++.h>
using namespace std;
function<void(void)> ____ = [](){ios_base::sync_with_stdio(false); cin.tie(0); cout.tie(0);};
using LL = int_fast64_t;
const LL MOD = 1e9+7;
const int MAXN = 1e7+7;
char n[MAXN];
LL x1,a,b,c,d,e;
int k,base[13],len;
LL qpow(LL x, LL y){
LL ret = 1;
while(y){
if(y&1) ret = ret * x % MOD;
y >>= 1;
x = x * x % MOD;
}
return ret;
}
int f(LL x){ return (a*x*x*x*x+b*x*x*x+c*x*x+d*x+e-1) % k + 1; }
int dp[2][1<<13];
int main(){
scanf("%s %lld %lld %lld %lld %lld %lld %d",n,&x1,&a,&b,&c,&d,&e,&k);
int num = k;
len = strlen(n);
if(len<6){
int tp = 0;
for(int i = 0; i < len ; i++) tp = tp * 10 + n[i] - '0';
num = min(num,tp);
}
set<int> S;
for(int i = 1; i <= num; i++, x1 = f(x1)){
if(S.count(x1)) break;
S.insert(x1);
}
int tg = 0;
dp[tg][0] = 1;
for(int x : S){
tg ^= 1;
memset(dp[tg],0,sizeof(dp[tg]));
for(int i = 0; i < 4096; i++) dp[tg][i] = (dp[tg^1][i] + dp[tg^1][i^x]) % MOD;
}
LL sum = 0; for(int i = 1; i < 4096; i++) sum = (sum + dp[tg][i]) % MOD;
LL pw = 0;
for(int i = 0; i < len; i++) pw = (pw * 10 + n[i] - '0') % (MOD - 1);
LL pn = qpow(2,pw);
printf("%lld\n",(sum*pn%MOD*qpow(qpow(2,S.size()),MOD-2)%MOD+MOD)%MOD);
return 0;
}
L. Magical Girl Haze
分层图最短路,\(dist[i][j]\)表示到第\(i\)个位置,消耗变\(0\)用了\(j\)次的最小消耗
//#pragma GCC optimize("O3")
//#pragma comment(linker, "/STACK:1024000000,1024000000")
#include<bits/stdc++.h>
using namespace std;
function<void(void)> ____ = [](){ios_base::sync_with_stdio(false); cin.tie(0); cout.tie(0);};
const int MAXN = 2e5+7;
using LL = int_fast64_t;
const LL INF = 0x3f3f3f3f3f3f3f3f;
LL dist[MAXN][11];
int n,m,k;
vector<pair<int,int> > G[MAXN];
LL Dijkstra(){
memset(dist,0x3f,sizeof(dist));
priority_queue<pair<LL,pair<int,int>>,vector<pair<LL,pair<int,int>>>,greater<pair<LL,pair<int,int>>> > que;
dist[1][0] = 0;
que.push(make_pair(dist[1][0],make_pair(1,0)));
while(!que.empty()){
auto p = que.top();
que.pop();
if(dist[p.second.first][p.second.second]!=p.first) continue;
int u = p.second.first, deg = p.second.second;
for(auto e : G[u]){
int v = e.first, w = e.second;
if(dist[u][deg]+w<dist[v][deg]){
dist[v][deg] = dist[u][deg] + w;
que.push(make_pair(dist[v][deg],make_pair(v,deg)));
}
if(deg!=k and dist[u][deg]<dist[v][deg+1]){
dist[v][deg+1] = dist[u][deg];
que.push(make_pair(dist[v][deg+1],make_pair(v,deg+1)));
}
}
}
LL ret = INF;
for(int i = 0; i <= k; i++) ret = min(ret,dist[n][i]);
return ret;
}
void solve(){
scanf("%d %d %d",&n,&m,&k);
for(int i = 1; i <= n; i++) G[i].clear();
for(int i = 1; i <= m; i++){
int u, v, w;
scanf("%d %d %d",&u,&v,&w);
G[u].push_back(make_pair(v,w));
}
printf("%lld\n",Dijkstra());
}
int main(){
int T;
for(scanf("%d",&T); T; T--) solve();
return 0;
}