传送门
题意:给一副边权非负整数的无向连通图,节点是 1 − N 1-N 1−N,问从1号节点出发,到达N号,使得路径上经过的边的权值XOR和最大。
思路:想到对图DFS,生成了一个树(怎么生成这一颗树不重要,但我们知道了1-N的路径),对于dfs中已经访问到的点还有说明能成环。能成环说明我可以走这个环也可以不走。
然后环的XOR边权值可选可不选。就可以想到用线性基.
总结: 初学线性基,很好用也很巧妙的算法实现,其思想是高斯消元。然后在这题中对图的dfs中判定环的想法学到了。这题做完有些豁然开朗。
参考博客:[学习笔记]线性基
MashiroSky
ACcode:
#include
#define ll long long
#define ld long double
#define ull unsigned long long
#define rep(i,a,b) for(int i=a;i<=b;i++)
ll gcd(ll a,ll b){ return b? gcd(b,a%b):a;}
const int N=2e5+10;
const ll P=1e9+7;
ll read(){
ll s = 0, f = 1; char ch = getchar();
while(!isdigit(ch)){
if(ch == '-') f = -1;
ch = getchar();
}
while(isdigit(ch)) s = (s << 3) + (s << 1) + (ch ^ 48), ch = getchar();
return s * f;
}
using namespace std;
int n,m;
int head[N],ver[N],nex[N];
int vis[N],sum;
ll c[N],dis[N],w[N];
int cnt,tot;
void addedge(int u,int v,ll val){
ver[++cnt]=v;
w[cnt]=val;
nex[cnt]=head[u];
head[u]=cnt;
}
void dfs(int u){
vis[u]=1;
for(int i=head[u];i;i=nex[i]){
int v=ver[i];
ll val=w[i];
if(!vis[v]){
dis[v]=dis[u]^val;
dfs(v);
}else c[++sum]=dis[v]^dis[u]^val;
}
}
// 线性基 板子
struct L_B{
// d[i] 二进制最高位是第i位. p[i]就是第2^i个小的数是p[i]
long long d[61],p[61]; // 顶值1e18
int cnt; // p数组有效个数
L_B()
{
memset(d,0,sizeof(d));
memset(p,0,sizeof(p));
cnt=0;
}
//插入
bool insert(long long val)
{
for (int i=60;i>=0;i--)
if (val&(1ll<<i))
{
if (!d[i])
{
d[i]=val;
break;
}
val^=d[i];
}
return val>0;
}
//得到最大值
long long query_max()
{
long long ret=0;
for (int i=60;i>=0;i--)
if ((ret^d[i])>ret)
ret^=d[i];
return ret;
}
//得到最小值
long long query_min()
{
for (int i=0;i<=60;i++)
if (d[i])
return d[i];
return 0;
}
// 题目中问到第k小的数是多少时,使用p数组
void rebuild()
{
for (int i=60;i>=0;i--)
for (int j=i-1;j>=0;j--)
if (d[i]&(1ll<<j))
d[i]^=d[j];
for (int i=0;i<=60;i++)
if (d[i])
p[cnt++]=d[i];
}
// 找第几小的值
long long kthquery(long long k)
{
int ret=0;
if (k>=(1ll<<cnt))
return -1;
for (int i=60;i>=0;i--)
if (k&(1ll<<i))
ret^=p[i];
return ret;
}
};
void solve(){
scanf("%d %d",&n,&m);
int u,v;
ll val;
rep(i,1,m){
scanf("%d %d %lld",&u,&v,&val);
addedge(u,v,val);
addedge(v,u,val);
}
dfs(1);
ll ans=dis[n];
L_B x;
rep(i,1,sum) x.insert(c[i]);
for(int i=60;i>=0;i--) {
ans=max(ans,ans^x.d[i]); }
printf("%lld\n",ans);
return ;
}
int main (){
// freopen("in.txt","r",stdin);
//freopen("out.txt","w",stdout);
solve();
getchar();
getchar();
return 0;
}
传送门
题意:
也就是在传统的NIM游戏中,多了一回合特殊开始。
思路:根据Nim的游戏规则,必胜则 ∑ x o r ( a [ i ] ) \sum xor(a[i]) ∑xor(a[i]), 现在我先手,我选了若干堆后。你的选择若干堆后,是希望 ∑ x o r ( a [ i ] ) = 0 \sum xor(a[i])=0 ∑xor(a[i])=0,这样你才能赢。现在对于我来说我的选择需要避免你能实现 ∑ x o r ( a [ i ] ) = 0 \sum xor(a[i])=0 ∑xor(a[i])=0。这样我才能赢。
什么情况我是必赢的呢? 我取了一部分后, ∑ x o r ( a [ i ] ) ≠ 0 \sum xor(a[i])\neq 0 ∑xor(a[i])=0,然后你无论怎么取一部分,都不能满足 ∑ x o r ( a [ i ] ) = 0 \sum xor(a[i]) = 0 ∑xor(a[i])=0。
现在的问题就变成了:我怎么取这一部分,使得原 X O R XOR XOR中任取后, ∑ X O R \sum XOR ∑XOR的线性基里不能有包含0的情况。所以我要删除若干个数,使得剩下的数的线性基不能组成0的情况。 这里的删除的数删哪呢,怎么删呢?猜一猜,推一推,贪心一下。可以得知从最小的数开始删除。
总结:NIM博弈论,线性基+贪心
ACcode:
#include
#define ll long long
#define ld long double
#define ull unsigned long long
#define rep(i,a,b) for(int i=a;i<=b;i++)
ll gcd(ll a,ll b){ return b? gcd(b,a%b):a;}
const int N=2e5+10;
const ll P=1e9+7;
ll read(){
ll s = 0, f = 1; char ch = getchar();
while(!isdigit(ch)){
if(ch == '-') f = -1;
ch = getchar();
}
while(isdigit(ch)) s = (s << 3) + (s << 1) + (ch ^ 48), ch = getchar();
return s * f;
}
using namespace std;
// 线性基 板子
struct L_B{
// d[i] 二进制最高位是第i位. p[i]就是第2^i个小的数是p[i]
long long d[61],p[61]; // 顶值1e18
int cnt; // p数组有效个数
L_B()
{
memset(d,0,sizeof(d));
memset(p,0,sizeof(p));
cnt=0;
}
//插入
bool insert(long long val)
{
for (int i=60;i>=0;i--)
if (val&(1ll<<i))
{
if (!d[i])
{
d[i]=val;
break;
}
val^=d[i];
}
return val>0;
}
//得到最大值
long long query_max()
{
long long ret=0;
for (int i=60;i>=0;i--)
if ((ret^d[i])>ret)
ret^=d[i];
return ret;
}
//得到最小值
long long query_min()
{
for (int i=0;i<=60;i++)
if (d[i])
return d[i];
return 0;
}
// 题目中问到第k小的数是多少时,使用p数组
void rebuild()
{
for (int i=60;i>=0;i--)
for (int j=i-1;j>=0;j--)
if (d[i]&(1ll<<j))
d[i]^=d[j];
for (int i=0;i<=60;i++)
if (d[i])
p[cnt++]=d[i];
}
// 找第几小的值
long long kthquery(long long k)
{
int ret=0;
if (k>=(1ll<<cnt))
return -1;
for (int i=60;i>=0;i--)
if (k&(1ll<<i))
ret^=p[i];
return ret;
}
};
ll a[200];
bool flag[200];
void solve(){
int n;
n=read();
rep(i,1,n){
a[i]=read();
}
sort(a+1,a+1+n);
ll sum=0;
for(int i=1;i<=n;i++){
flag[i]=1;
L_B x;
for(int j=1;j<=n;j++){
if(flag[j]==0) x.insert(a[j]);
}
bool ans=x.insert(a[i]);
if(ans==0) sum+=a[i];
else flag[i]=0;
}
cout<<sum<<endl;
}
int main (){
// freopen("in.txt","r",stdin);
//freopen("out.txt","w",stdout);
solve();
getchar();
getchar();
return 0;
}
传送门
题意:
求有多少种长度为 n n n 的序列 A A A.
满足以下条件: 1 1 1 ~ n n n 这 n n n 个数在序列中各出现了一次若第 i i i个数 A [ i ] A[i] A[i] 的值为 i i i,则称 i i i 是稳定的。序列恰好有 m m m 个数是稳定的
满足条件的序列可能很多,序列数对 10^9+7 取模。
思路:
前置知识错位排序: f ( n ) = ( f ( n − 1 ) + f ( n − 2 ) ) ∗ ( n − 1 ) f ( 1 ) = 0 , f ( 0 ) = 1 ; f(n)=(f(n-1)+f(n-2))*(n-1) f(1)=0,f(0)=1; f(n)=(f(n−1)+f(n−2))∗(n−1)f(1)=0,f(0)=1;
情况1:插入第i个元素时,前 i − 1 i-1 i−1个已经错位排好,则选择其中任意一个与第i个互换一定满足要求,选择方法共 i − 1 i-1 i−1种,前 i − 1 i-1 i−1位错排 f [ i − 1 ] f[i-1] f[i−1]种,记 f [ i − 1 ] ∗ ( i − 1 ) f[i-1] * (i-1) f[i−1]∗(i−1)
情况2:插入第i个元素时,前 i − 1 i-1 i−1个中恰有一个元素 a [ j ] a[j] a[j]使得 a [ j ] = j a[j]=j a[j]=j,其他i-2个错位排好,则将 i i i与 j j j交换, j j j在 i − 2 i-2 i−2位中的插入共 i − 1 i-1 i−1种,前i-2位错排f[i-2]种,记 f [ i − 2 ] ∗ ( i − 1 ) f[i-2]*(i-1) f[i−2]∗(i−1)
ACcode
#include
#define ll long long
#define ld long double
#define ull unsigned long long
#define rep(i,a,b) for(int i=a;i<=b;i++)
ll gcd(ll a,ll b){ return b? gcd(b,a%b):a;}
const int N=1e6+10;
const ll P=1e9+7;
ll read(){
ll s = 0, f = 1; char ch = getchar();
while(!isdigit(ch)){
if(ch == '-') f = -1;
ch = getchar();
}
while(isdigit(ch)) s = (s << 3) + (s << 1) + (ch ^ 48), ch = getchar();
return s * f;
}
using namespace std;
ll qpow(ll a,ll x){
ll ans=1;
while(x){
if(x&1){
ans=(ans*a)%P;
}
a=(a*a)%P;
x>>=1;
}
return ans;
}
ll a[N];
//ll inv[N];
ll f[N];
void pre(){
a[0]=1;
for(int i=1;i<=1e6;i++){
a[i]=(a[i-1]*i)%P;
}
// for(int i=1;i<=2e7;i++){
// inv[i]=qpow(a[i],P-2);
// }
f[0]=1;
f[1]=0;
rep(i,2,1e6){
f[i]=(f[i-1]+f[i-2])*(i-1)%P;
}
// cout<
return ;
}
ll C(ll n,ll m){
if(n<m) return 0;
// printf("%d %d %d\n",a[n],inv[m],inv[n-m]);
return (a[n]*qpow(a[m],P-2)%P *qpow(a[n-m],P-2))%P;
}
void solve(){
// printf("%d %d\n",qpow(2,10),C(6,2));
ll n,m;
n=read();
m=read();
ll sum=C(n,m)*f[n-m]%P;
printf("%lld\n",sum);
return ;
}
int main (){
// freopen("in.txt","r",stdin);
// freopen("out.txt","w",stdout);
pre();
int T=read();
while(T--)
solve();
getchar();
getchar();
return 0;
}
题意: 给 N N N个数,求解 g c d ( a , b , c , d ) = = 1 gcd(a,b,c,d)==1 gcd(a,b,c,d)==1 的个数有多少。
思路: 容斥原理,经典中的经典。正面求解困难,求相反问题,四元数不互质的个数是多,然后 总 数 − 它 总数-它 总数−它。
考虑不互质的情况。当我们得出了 d = 2 , d = 3 d=2,d=3 d=2,d=3的个数,那么在 d = 6 d=6 d=6时会重复,所以要减去。
总结: 莫比乌斯反演与容斥原理
ACcode
#include
#include
#define ll long long
#define ld long double
#define ull unsigned long long
#define rep(i,a,b) for(int i=a;i<=b;i++)
ll gcd(ll a,ll b){ return b? gcd(b,a%b):a;}
const int N=2e5+10;
const ll P=1e9+7;
ll read(){
ll s = 0, f = 1; char ch = getchar();
while(!isdigit(ch)){
if(ch == '-') f = -1;
ch = getchar();
}
while(isdigit(ch)) s = (s << 3) + (s << 1) + (ch ^ 48), ch = getchar();
return s * f;
}
using namespace std;
ll d[N];
int v[N],prime[N],miu[N];
void get_miu(int n){
int m=0;
miu[1]=1;
for(int i=2;i<=n;i++){
if(v[i]==0){
v[i]=i;
prime[++m]=i;
miu[i]=-1;
}
for(int j=1;j<=m;j++){
if(prime[j] > v[i] or prime[j]*i>n) break;
v[i*prime[j]]=prime[j];
if(miu[i]==0) miu[i*prime[j]]=0;
if(v[i]==prime[j]) miu[i*prime[j]]=0;
else miu[i*prime[j]]=-miu[i];
}
}
// rep(i,1,100){
// printf("i:%d miu:%d\n",i,miu[i]);
// }
return ;
}
void pre(int k){
for(int i=1;i*i<=k;i++){
if(k%i!=0) continue;
if(i!=k/i){
d[i]++;
d[k/i]++;
}else d[i]++;
}
return ;
}
void solve(){
int n;
while(scanf("%d",&n)==1){
int maxn=0;
int r;
rep(i,1,n){
pre(r=read());
maxn=max(maxn,r);
}
ll ans=0;
for(int i=1;i<=maxn;i++){
ll tot=d[i]*(d[i]-1)*(d[i]-2)*(d[i]-3)/24;
ans+=miu[i]*tot;
}
printf("%lld\n",ans);
for(int i=1;i<=maxn;i++){
d[i]=0;
}
}
return ;
}
int main (){
// freopen("in.txt","r",stdin);
//freopen("out.txt","w",stdout);
get_miu(10000);
solve();
// getchar();
// getchar();
return 0;
}
// submitted by HNUST26
#include
#define ll long long
#define ld long double
#define ull unsigned long long
#define rep(i,a,b) for(int i=a;i<=b;i++)
ll gcd(ll a,ll b){ return b? gcd(b,a%b):a;}
const int N=2e5+10;
const ll P=1e9+7;
ll read(){
ll s = 0, f = 1; char ch = getchar();
while(!isdigit(ch)){
if(ch == '-') f = -1;
ch = getchar();
}
while(isdigit(ch)) s = (s << 3) + (s << 1) + (ch ^ 48), ch = getchar();
return s * f;
}
using namespace std;
int a[1010];
int dfs(int ans){
if(a[ans]!=-1) return a[ans];
int sum=0;
int b[1030];
memset(b,0,sizeof(b));
for(int i=1;i*i<=ans;i++){
if(ans%i==0){
if(i!=ans/i){
sum^=dfs(i);
if(i!=1) sum^=dfs(ans/i);
}else sum^=dfs(i);
}
}
for(int i=1;i*i<=ans;i++){
if(ans%i==0)
{
b[sum^dfs(i)]=1;
if(i!=1) b[sum^dfs(ans/i)]=1;
}
}
for(int i=0;;i++){
if(b[i]==0){
a[ans]=i;
// cout<
return i;
}
}
}
void solve(){
int n;
memset(a,-1,sizeof(a));
a[1]=0;
while(scanf("%d",&n)!=EOF){
int ans=0;
rep(i,1,n){
ans^=dfs(read());
}
if(ans==0) cout<<"rainbow"<<endl;
else cout<<"freda"<<endl;
}
return ;
}
int main (){
// freopen("in.txt","r",stdin);
//freopen("out.txt","w",stdout);
solve();
getchar();
getchar();
return 0;
}
题意:排成直线的格子上放有 n n n个棋子。棋子i在左数第 p i p_i pi的格子上, G e o r g i a Georgia Georgia 和 B o b Bob Bob轮流选择一个棋子向左移动,每次可以移动一格及任意多格,但是不允许反超其他的棋子,也不允许将两个棋子凡在同一个格子内。无法移动操作的一方失败。Georgia 先移动。
思路:
我最先想到的思路是,排个序, a [ i ] a[i] a[i]的棋子最终在 i i i的位置,具体实现如下:
for(int i=1;i<=n;i++){
sum^=(a[i]-left-1);
left++;
}
当时WA了,我恨不能理解为什么。于是我返过头又看了看NIM博弈的原理是什么呢?
大概就是我要胜出的话,我需要抛出a1^ a2 ^ a3…^an=0给对方。那就是原理2了。所以呢,要满足NIM博弈还有一个要求的满足的条件是
每一推都要独立,不能受到影响。 而我在写的代码思路中,是一堆一堆石子取完,再取下一堆石子。而NIM博弈中我可以去一部分第一堆的石子,而后下一步可以从第二堆里去一部分石子。所以我的博弈想法出现了每一堆石子出现的先后的取,有影响。所以思路错误。
而这里"只要将所加部分减回去就回到了原来的状态"。我的理解是我的博弈状态还是希望对方面临的异或和是0,我就能胜利。所以我并不想打破这种我能"胜利的局面",所以在原基础上我再减就好了。(有点像"敌不动,我不动",hmm我举的比喻,还算清楚吗?),所以在这里可以把他们看成一对一对的来处理,每一堆的石子是并没有影响的,就是独立的喽。
总结: 在做博弈论的题目时,先理清题意的要求,在使用结论时,始终依NIM博弈的三定理为依据且在策略中不能违背。
ACcode:
#include
#include
#define ll long long
#define ld long double
#define ull unsigned long long
#define rep(i,a,b) for(int i=a;i<=b;i++)
ll gcd(ll a,ll b){ return b? gcd(b,a%b):a;}
const int N=2e5+10;
const ll P=1e9+7;
ll read(){
ll s = 0, f = 1; char ch = getchar();
while(!isdigit(ch)){
if(ch == '-') f = -1;
ch = getchar();
}
while(isdigit(ch)) s = (s << 3) + (s << 1) + (ch ^ 48), ch = getchar();
return s * f;
}
using namespace std;
ll n,m;
int a[1010];
void solve(){
int T=read();
while(T--){
n=read();
rep(i,1,n) a[i]=read();
ll sum=0;
int i=1;
sort(a+1,a+1+n);
if(n&1) sum^=(a[1]-1),i++;
for(;i+1<=n;i+=2){
sum^=(a[i+1]-a[i]-1);
}
if(sum==0){
printf("Bob will win\n");
}else printf("Georgia will win\n");
}
}
int main (){
// freopen("in.txt","r",stdin);
//freopen("out.txt","w",stdout);
solve();
getchar();
getchar();
return 0;
}
未完待续,持续更新中…