A 签到
C 推式子,思维,int128
E 几何,扩展欧几里得
G 树形dp,图论结论 ,记忆化搜索
J 拓扑排序 ,贪心构造】
M 树状数组求逆序对,双端队列模拟
L 博弈纳什平衡
签到,没啥说的
#include <bits/stdc++.h> using namespace std; typedef long long ll; int vis[1005]; int main() { ios::sync_with_stdio(false), cin.tie(0), cout.tie(0); int n; cin >> n; string str; cin >> str; str = "?" + str; for (int i = 1; i <= n; i++) { if (str[i] == 'L') vis[i - 1] = vis[i] = vis[i + 1] = 1; } for (int i = 1; i <= n; i++) { if (vis[i]) cout << str[i]; else cout << "C"; } return 0; }
手推不难发现,只要是进行一次翻转操作,之后无论进行任何操作,得到的结果都是一样的。所以我们可以枚举第几次使用了翻转操作。我们现在需要获取的是,翻转操作之前数组的各项情况,再把他当成一个新的数组来求最终的结果。画图会好理解一些。注意开int128,否则会爆炸
#include <bits/stdc++.h> using namespace std; typedef __int128 ll; # define mod 1000000007 inline __int128 read() { __int128 x=0,f=1; char ch=getchar(); while(ch<'0'||ch>'9') { if(ch=='-') f=-1; ch=getchar(); } while(ch>='0'&&ch<='9') { x=x*10+ch-'0'; ch=getchar(); } return x*f; } inline void write(__int128 x) { if(x<0) { putchar('-'); x=-x; } if(x>9) write(x/10); putchar(x%10+'0'); } ll a[1000000+10],sum[1000000+10]; ll bac[1000000+10],bacs; ll cnt[1000000+10]; ll inv; ll qp(ll base, ll pow) { ll ans=1; while(pow) { if(pow&1) ans=ans*base%mod; pow>>=1; base=base*base%mod; } return ans; } ll getsum(ll b,ll e,ll xiang) { b+=mod; b%=mod; e+=mod; e%=mod; xiang+=mod; xiang%=mod; return (b+e)%mod*xiang%mod*inv%mod; } int main() { inv=qp(2ll,mod-2); ll n,m; n=read(); m=read(); ll s=0; for(ll i=1;i<=n;i++) { a[i]=read(); sum[i]=sum[i-1]+a[i]; sum[i]%=mod; s+=sum[i]; s%=mod; } cnt[0]=1; for(ll i=1;i<=1000000;i++) { cnt[i]=cnt[i-1]*2ll%mod; } for(int i=n;i>=1;i--) { bac[i]=bac[i+1]+a[i]; bac[i]%=mod; bacs+=bac[i]; bacs%=mod; } ll ans=0; for(ll i=1;i<=m+1;i++) { ll nowcnt=cnt[i-1]%mod; ll temppre=nowcnt*s%mod; ll tempbac=nowcnt*bacs%mod; ll fuck=getsum(n*sum[n]%mod,(nowcnt-1+mod)*n%mod*sum[n]%mod,nowcnt-1); ll fuckbac=getsum(n*bac[1]%mod,(nowcnt-1)*n%mod*bac[1]%mod,nowcnt-1); fuck+=temppre%mod; fuck%=mod; if(i==m+1) { ans=max(ans,fuck); break; } fuckbac+=tempbac%mod; fuckbac%=mod; ll nexcnt=cnt[m-i]; ll nown=cnt[i-1]*n%mod; ll sumbac=fuckbac*nexcnt%mod; ll sumpre=fuck*nexcnt%mod; tempbac=cnt[i-1]*bac[1]%mod; temppre=cnt[i-1]*sum[n]%mod; ll bacbac=getsum(nown*tempbac%mod,(nexcnt-1)*nown*tempbac%mod,(nexcnt-1)%mod); bacbac*=2ll; bacbac%=mod; ll prepre=getsum(nown*temppre%mod,(nexcnt-1)*nown*temppre%mod,(nexcnt-1)%mod); prepre*=2ll; prepre%=mod; bacbac+=nexcnt*nown%mod*tempbac%mod; bacbac%=mod; if(i==1) ans=max(ans,((sumbac+sumpre)%mod+(bacbac+prepre)%mod)%mod); } write(ans); return 0; }
需要一点向量叉乘求三角形面积的知识,有(x1,y1) (x2,y2),另有 (x3,y3)未知
AB (x1-x2.y1-y2)
AC (x1-x3,y1-y3)
1/2 * (x1-x2)*(y1-y3) + (x1-x3)*(-y1+y2)
然后已知的作为x,y,未知的作为系数a,b,一次扩欧就行
#include <bits/stdc++.h> using namespace std; typedef long long ll; ll exgcd(ll a, ll b,ll &x,ll&y) { if(b==0) { y=0; x=1; return a; } ll temp=exgcd(b,a%b,x,y); ll yy=y; y=x-a/b*y; x=yy; return temp; } int main() { int t; cin>>t; while(t--) { ll x1,y1,x2,y2; cin>>x1>>y1>>x2>>y2; ll a=x2-x1; ll b=y1-y2; ll x,y; exgcd(a,b,x,y); x+=y1; y+=x1; cout<<y<<" "<<x<<endl; } return 0; }
两种情况,一种没有交点,也就是两条链分居在某一条边的两侧。一种是有交点,画图分析得,只有一个焦点的时候最优,进一步转化为一个点连接的四条链最大值。
很多人用树形dp,换根去做,其实推起来很复杂。不如利用树确定父亲以及儿子便可以决定今后遍历顺序这一性质,外加父亲儿子关系不超过2*n这一条件,直接记忆化。
先爆搜每个点连接的四条链,取max, 再断开每一条边分别求两侧链条最大值。这一过程可以合并一个点连接的两条链,也可以取更深一层搜索获得的链,二者取最大值即可
#include <bits/stdc++.h> using namespace std; typedef long long ll; unordered_map<int,int>mp[200000+10]; int a[1000000+10]; vector<int>v[200000+10]; int x[200000+10],y[200000+10]; vector<int>maxx[200000+10]; inline int dfs1(int now,int pre) { if(mp[now][pre]) return mp[now][pre]; int nowans=0; for(auto it:v[now]) { if(it==pre) continue; int temp=dfs1(it,now)+a[it]; nowans=max(nowans,temp); if(pre==0) maxx[now].push_back(temp); } return mp[now][pre]=nowans; } bool cmp(int x,int y) { return x>y; } unordered_map<int,int>dp2[200000+10]; inline int dfs2(int now,int pre) { if(dp2[now][pre]) return dp2[now][pre]; int maxx1=0,maxx2=0,maxx3=0; for(auto it:v[now]) { if(it==pre) continue; int temp=dfs1(it,now)+a[it]; if(temp>maxx1) { maxx2=maxx1; maxx1=temp; } else { maxx2=max(maxx2,temp); } maxx3=max(maxx3,dfs2(it,now)); } return dp2[now][pre]=max(maxx3,maxx1+maxx2+a[now]); } int main() { int n; cin>>n; for(int i=1; i<=n; i++) { cin>>a[i]; } for(int i=1; i<n; i++) { cin>>x[i]>>y[i]; v[x[i]].push_back(y[i]); v[y[i]].push_back(x[i]); } int ans=0; for(int i=1; i<=n; i++) { dfs1(i,0); sort(maxx[i].begin(),maxx[i].end(),cmp); int cnt=0,now=0; for(auto it:maxx[i]) { cnt++; if(cnt>4) break; now+=it; } ans=max(ans,now); } for(int i=1; i<n; i++) { ans=max(ans,dfs2(x[i],y[i])+dfs2(y[i],x[i])); } cout<<ans; return 0; }
首先大小关系是可以利用拓扑排序的次序来实现的。严格按照小指向大的方式连边,获取每个位置最小值的时候,拓扑序上每次取max。获取最大值的时候,建立反图,每次取min。详见代码。这样我们获得了取值的[L,R]。现在就变成了一个经典贪心问题。考虑从1-n开始构造答案。按照左端点进行排序,每次把<=i的全部左端点的右端点都加入set。贪心的想,我们一定是把当前右端点最小的给赋值,因为更大一些的可以在i后面被赋到。
#include <bits/stdc++.h> using namespace std; typedef long long ll; vector<int>v1[200000+10],v2[200000+10]; int du1[200000+10],du2[200000+10],L[200000+10],R[200000+10],a[200000+10]; int dp1[200000+10],dp2[200000+10]; bool cmp(int x,int y) { return L[x]<L[y]; } int ans[200000+10],b[200000+10]; int main() { int t; cin>>t; while(t--) { int n,m; cin>>n>>m; for(int i=1; i<=n; i++) { du1[i]=du2[i]=0; v1[i].clear(); v2[i].clear(); cin>>a[i]; if(a[i]) { L[i]=a[i]; R[i]=a[i]; } else { L[i]=1; R[i]=n; } } for(int i=1; i<=m; i++) { int x,y; scanf("%d%d",&x,&y); v1[x].push_back(y); v2[y].push_back(x); du1[y]++; du2[x]++; } queue<int>q; for(int i=1; i<=n; i++) { if(du1[i]==0) { q.push(i); } } while(!q.empty()) { int now=q.front(); q.pop(); for(auto it:v1[now]) { du1[it]--; L[it]=max(L[it],L[now]+1); if(du1[it]==0) { q.push(it); } } } for(int i=1; i<=n; i++) { if(du2[i]==0) { q.push(i); } } while(!q.empty()) { int now=q.front(); q.pop(); for(auto it:v2[now]) { du2[it]--; R[it]=min(R[it],R[now]-1); if(du2[it]==0) { q.push(it); } } } int flag=0; set<pair<int,int>>s; for(int i=1; i<=n; i++) { b[i]=i; } sort(b+1,b+1+n,cmp); int nowpos=1; for(int i=1; i<=n; i++) { while(nowpos<=n&&L[b[nowpos]]<=i) { s.insert(make_pair(R[b[nowpos]],b[nowpos])); nowpos++; } if(s.empty()) { flag=1; break; } auto it=*s.begin(); if(it.first<i) { flag=1; break; } ans[it.second]=i; s.erase(s.begin()); } if(flag) { cout<<-1<<'\n'; } else { for(int i=1;i<=n;i++) { if(ans[i]!=a[i]&&a[i]) { flag=1; break; } } if(flag) { cout<<-1<<endl; continue; } for(int i=1; i<=n; i++) { cout<<ans[i]<<" "; } cout<<'\n'; } } return 0; }
第一个答案就是逆序对个数,直接树状数组。
然后就是每次把首部移动到尾部,实质就是把小于首部的数量减去,加上大于首部的
翻转操作就是逆序对x变成n*n(-1)/2-x,然后每次把双端队列尾部移动要首部即可。
注意long long
#include <bits/stdc++.h> using namespace std; typedef long long int ll; # define mod 10 ll n,m; ll sum[300000+10]; deque<ll>d; ll lowbit(ll x) { return x&-x; } void change(ll pos) { while(pos<=n) { sum[pos]++; pos+=lowbit(pos); } } ll getsum(ll pos) { ll ans=0; while(pos) { ans+=sum[pos]; pos-=lowbit(pos); } return ans; } ll a[300000+10]; ll fuck[1000000+10]; string s; int main() { cin>>n>>m; for(ll i=1; i<=n; i++) { scanf("%lld",&a[i]); d.push_back(a[i]); } ll ans=0; for(ll i=n; i>=1; i--) { ans+=getsum(a[i]-1); change(a[i]); } cin>>s; cout<<ans<<endl; ll flag=0; ll temp=n*(n-1)/2; temp%=mod; for(int i=0; i<s.length(); i++) { if(s[i]=='R') { flag^=1; ans=temp-ans; ans%=mod; ans+=mod; ans%=mod; } else { if(flag==0) { int now=d.front(); ans-=(now-1); ans%=mod; ans+=mod; ans%=mod; ans+=(n-now); ans%=mod; ans+=mod; ans%=mod; d.pop_front(); d.push_back(now); } else { int now=d.back(); ans-=(now-1); ans%=mod; ans+=mod; ans%=mod; ans+=(n-now); ans%=mod; ans+=mod; ans%=mod; d.pop_back(); d.push_front(now); } } fuck[i]=ans%mod; } for(int i=0; i<m; i++) { fuck[i]%=mod; fuck[i]+=mod; fuck[i]%=mod; cout<<fuck[i]; } return 0; }