Dynamic Programming in Game Theory for Competitive Programming
Last Updated :
18 Jan, 2024
In the fast-paced world of competitive programming, mastering dynamic programming in game theory is the key to solving complex strategic challenges. This article explores how dynamic programming in game theory can enhance your problem-solving skills and strategic insights, giving you a competitive edge. Whether you’re a seasoned coder or a newcomer, this article uncover the power of Dynamic Programming in Game Theory for Competitive Programming.
Problem Identification of Dynamic Programming in Game Theory:
In these type of problems you are basically given 2 players(first and second), set of operations, and a win condition. Given that both the players play optimally and they take turn one after the other we have to determine the winner of the game.
Winning State and Loosing State:
If a player is standing at a particular state ‘S’ and it can send its opponent to one of the K different states T1, T2…TK then:
- Condition for ‘S’ to be a winning State: If there exists atleast one state from T1 to TK which is a Loosing States.
- Condition for ‘S’ to be a Loosing State: All the states from T1 to TK are Winning States.
Obviously, in order to win the player will try to send its opponent to any loosing state and if it is not possible to do so then that player itself looses the game. follow the below image for better understanding.
Lets’s take a look over some problems to understand this concept more thoroughly:
Problem 1:
Two players (First and Second) are playing a game on a 2-D plane in which a Token has been place at coordinate (0,0). Players can perform two types of operations on the token:
- Increase the X coordinate by exactly K distance
- Increase the Y coordinate by exactly K distance
You are given an integer D, In order to perform above operations the player must ensure that the token stays within Euclidean distance D from (0,0) i.e. after each operation, X2 + Y2 <= D2 where X and Y are the coordinates of the token.
Given the value of K and D, determine the Winner of the Game assuming both the players play optimally and First player plays first.
Example:
Input: D=2, K=1
Output: Second
Explanation: First player moves token from (0,0) to (0,1)
Second player moves token from (0,1) to (0,2)
Now, whatever move First player makes, the distance will exceed the Euclidean Distance
Input: D=5, K=2
Output: Second
Approach:
Let DP[i][j] denote whether the coordinate (i,j) are a winning state or a loosing state for any player that is:
- DP[i][j] = 0 means a loosing state
- DP[i][j] = 1 means a winning state
Recurrence Relation:
On the basis of given operations we can either increment x coordinate by K or y coordinate by K, hence DP[i][j] depends on two values:
Condition for (i, j) to be winning state i.e. DP[i][j] = 1 :
- DP[i+k][j] = 0 OR DP[i][j+k] = 0 (Why? read the definition of winning state)
Condition for (i, j) to be loosing state i.e. DP[i][j] = 0:
- DP[i+k][j] = 1 AND DP[i][j+k] = 1 (Why? read the definition of Loosing state)
Below is the implementation of the above approach:
C++
#include <bits/stdc++.h>
#define ll long long
using namespace std;
ll d, k;
ll findWinner(ll i, ll j, vector<vector< int > >& dp)
{
ll ans = 1;
if (dp[i][j] != -1)
return dp[i][j];
ll x = (i + k) * (i + k) + j * j;
ll y = (i) * (i) + (j + k) * (j + k);
if (x <= d * d) {
ans = ans & (findWinner(i + k, j, dp));
}
if (y <= d * d) {
ans = ans & (findWinner(i, j + k, dp));
}
if (ans == 0)
return dp[i][j] = 1;
return dp[i][j] = 0;
}
int main()
{
d = 5;
k = 2;
vector<vector< int > > dp(d * d + 1,
vector< int >(d * d + 1, -1));
int ans = findWinner(0, 0, dp);
if (ans == 1) {
cout << "First player wins";
}
else
cout << "Second player wins";
cout << endl;
}
|
Java
import java.util.Arrays;
public class GameWinner {
private static int d, k;
public static int findWinner( int i, int j, int [][] dp) {
int ans = 1 ;
if (dp[i][j] != - 1 ) {
return dp[i][j];
}
int x = (i + k) * (i + k) + j * j;
int y = i * i + (j + k) * (j + k);
if (x <= d * d) {
ans &= findWinner(i + k, j, dp);
}
if (y <= d * d) {
ans &= findWinner(i, j + k, dp);
}
if (ans == 0 ) {
return dp[i][j] = 1 ;
}
return dp[i][j] = 0 ;
}
public static void main(String[] args) {
d = 5 ;
k = 2 ;
int [][] dp = new int [d * d + 1 ][d * d + 1 ];
for ( int [] row : dp) {
Arrays.fill(row, - 1 );
}
int ans = findWinner( 0 , 0 , dp);
if (ans == 1 ) {
System.out.println( "First player wins" );
} else {
System.out.println( "Second player wins" );
}
}
}
|
Python3
def findWinner(i, j, dp):
ans = 1
if dp[i][j] ! = - 1 :
return dp[i][j]
x = (i + k) * (i + k) + j * j
y = i * i + (j + k) * (j + k)
if x < = d * d:
ans = ans & findWinner(i + k, j, dp)
if y < = d * d:
ans = ans & findWinner(i, j + k, dp)
if ans = = 0 :
dp[i][j] = 1
else :
dp[i][j] = 0
return dp[i][j]
d = 5
k = 2
dp = [[ - 1 for _ in range (d * d + 1 )] for _ in range (d * d + 1 )]
ans = findWinner( 0 , 0 , dp)
if ans = = 1 :
print ( "First player wins" )
else :
print ( "Second player wins" )
|
C#
using System;
public class GFG {
static long d, k;
static long FindWinner( long i, long j, long [, ] dp)
{
long ans = 1;
if (dp[i, j] != -1)
return dp[i, j];
long x = (i + k) * (i + k) + j * j;
long y = i * i + (j + k) * (j + k);
if (x <= d * d) {
ans = ans & (FindWinner(i + k, j, dp));
}
if (y <= d * d) {
ans = ans & (FindWinner(i, j + k, dp));
}
if (ans == 0)
return dp[i, j] = 1;
return dp[i, j] = 0;
}
static void Main()
{
d = 5;
k = 2;
long [, ] dp = new long [d * d + 1, d * d + 1];
for ( int i = 0; i <= d * d; i++) {
for ( int j = 0; j <= d * d; j++) {
dp[i, j] = -1;
}
}
int ans = Convert.ToInt32(FindWinner(0, 0, dp));
if (ans == 1) {
Console.WriteLine( "First player wins" );
}
else {
Console.WriteLine( "Second player wins" );
}
}
}
|
Javascript
function findWinner(i, j, dp) {
let ans = 1;
if (dp[i][j] !== -1)
return dp[i][j];
let x = (i + k) * (i + k) + j * j;
let y = i * i + (j + k) * (j + k);
if (x <= d * d)
ans = ans & findWinner(i + k, j, dp);
if (y <= d * d)
ans = ans & findWinner(i, j + k, dp);
if (ans === 0)
return dp[i][j] = 1;
return dp[i][j] = 0;
}
let d = 5;
let k = 2;
let dp = new Array(d * d + 1).fill().map(() => new Array(d * d + 1).fill(-1));
let ans = findWinner(0, 0, dp);
if (ans === 1) {
console.log( "First player wins" );
}
else {
console.log( "Second player wins" );
}
|
Problem 2:
Two players (First and Second) are playing a game on array arr[] of size N. The players are building a sequence together, initially the sequence is empty. In one turn the player can perform either of the following operations:
- Remove the rightmost array element and append it to the sequence.
- Remove the leftmost array element and append it to the sequence.
The rule is that the sequence must always be strictly increasing, the winner is the player that makes the last move. The task is to determine the winner.
Example:
Input: arr=[5, 4, 5]
Output: First Player Wins
Explanation: After the first player append 5 into the sequence the array would look like either [4,5] or [5,4] and second player won’t be able to make any move.
Input: arr=[5, 8, 2, 1, 10, 9]
Output: Second Player Wins
Explanation: For any element the first player append to the sequence, the second player can always append a strictly greater elements.
Solution:
Let DP[L][R] denote whether the subarray from L to R is a Loosing state or a Winning state for any player that is:
- DP[L][R] = 0 means L to R forms a Loosing subarray.
- DP[L][R] = 1 means L to R forms a Winning subarray.
Recurrence Relation:
As the operations allow a player to only remove from the rightmost end or the leftmost end therefore DP[L][R] will depend on two ranges based on two conditions:
- DP[L+1][R] if we can append Arr[L] into our strictly increasing sequence.
- DP[L][R-1] if we can append Arr[R] into our strictly increasing sequence.
Condition for (L, R) to be winning state i.e. DP[L][R] = 1 :
- DP[L+1][R] =0 OR DP[L][R-1] = 0 (Why? read the definition of winning state)
Condition for (L, R) to be loosing state i.e. DP[L][R] = 0:
- DP[L+1][R] =1 AND DP[L][R-1] = 1 (Why? read the definition of Loosing state)
Below is the implementation of the above approach:
C++
#include <bits/stdc++.h>
#define ll long long
using namespace std;
ll findWinner(ll l, ll r, ll last, vector<ll>& arr,
vector<vector<ll> >& dp)
{
if (l > r) {
return 0;
}
ll x = arr[l];
ll y = arr[r];
ll ans = 1;
if (dp[l][r] != -1)
return dp[l][r];
if (x > last) {
ans = ans & (findWinner(l + 1, r, x, arr, dp));
}
if (y > last) {
ans = ans & (findWinner(l, r - 1, y, arr, dp));
}
if (ans == 0)
return dp[l][r] = 1;
else
return dp[l][r] = 0;
}
int main()
{
vector<ll> arr = { 5, 8, 2, 1, 10, 9 };
ll n = arr.size();
vector<vector<ll> > dp(n, vector<ll>(n, -1));
ll ans = findWinner(0, n - 1, INT_MIN, arr, dp);
if (ans) {
cout << "First player wins";
}
else
cout << "Second player wins";
}
|
Java
import java.util.Arrays;
public class GFG {
static long findWinner( int l, int r, long last,
long [] arr, long [][] dp)
{
if (l > r) {
return 0 ;
}
long x = arr[l];
long y = arr[r];
long ans = 1 ;
if (dp[l][r] != - 1 )
return dp[l][r];
if (x > last) {
ans &= (findWinner(l + 1 , r, x, arr, dp));
}
if (y > last) {
ans &= (findWinner(l, r - 1 , y, arr, dp));
}
if (ans == 0 )
return dp[l][r] = 1 ;
else
return dp[l][r] = 0 ;
}
public static void main(String[] args)
{
long [] arr = { 5 , 8 , 2 , 1 , 10 , 9 };
int n = arr.length;
long [][] dp = new long [n][n];
for ( long [] row : dp)
Arrays.fill(row, - 1 );
long ans = findWinner( 0 , n - 1 , Integer.MIN_VALUE,
arr, dp);
if (ans == 1 ) {
System.out.println( "First player wins" );
}
else {
System.out.println( "Second player wins" );
}
}
}
|
Python3
def find_winner(l, r, last, arr, dp):
if l > r:
return 0
x = arr[l]
y = arr[r]
ans = 1
if dp[l][r] ! = - 1 :
return dp[l][r]
if x > last:
ans = ans & find_winner(l + 1 , r, x, arr, dp)
if y > last:
ans = ans & find_winner(l, r - 1 , y, arr, dp)
if ans = = 0 :
dp[l][r] = 1
else :
dp[l][r] = 0
return dp[l][r]
arr = [ 5 , 8 , 2 , 1 , 10 , 9 ]
n = len (arr)
dp = [[ - 1 ] * n for _ in range (n)]
ans = find_winner( 0 , n - 1 , float ( '-inf' ), arr, dp)
if ans:
print ( "First player wins" )
else :
print ( "Second player wins" )
|
C#
using System;
using System.Collections.Generic;
public class GFG {
static long FindWinner( long l, long r, long last,
List< long > arr,
List<List< long > > dp)
{
if (l > r) {
return 0;
}
long x = arr[( int )l];
long y = arr[( int )r];
long ans = 1;
if (dp[( int )l][( int )r] != -1)
return dp[( int )l][( int )r];
if (x > last) {
ans = ans & (FindWinner(l + 1, r, x, arr, dp));
}
if (y > last) {
ans = ans & (FindWinner(l, r - 1, y, arr, dp));
}
if (ans == 0)
return dp[( int )l][( int )r] = 1;
else
return dp[( int )l][( int )r] = 0;
}
static void Main()
{
List< long > arr
= new List< long >{ 5, 8, 2, 1, 10, 9 };
long n = arr.Count;
List<List< long > > dp = new List<List< long > >();
for ( int i = 0; i < n; i++) {
dp.Add( new List< long >( new long [n]));
for ( int j = 0; j < n; j++) {
dp[i][j] = -1;
}
}
long ans
= FindWinner(0, n - 1, int .MinValue, arr, dp);
if (ans == 1) {
Console.WriteLine( "First player wins" );
}
else {
Console.WriteLine( "Second player wins" );
}
}
}
|
Javascript
function findWinner(l, r, last, arr, dp) {
if (l > r) {
return 0;
}
let x = arr[l];
let y = arr[r];
let ans = 1;
if (dp[l][r] !== -1) {
return dp[l][r];
}
if (x > last) {
ans = ans & (findWinner(l + 1, r, x, arr, dp));
}
if (y > last) {
ans = ans & (findWinner(l, r - 1, y, arr, dp));
}
if (ans === 0) {
return dp[l][r] = 1;
} else {
return dp[l][r] = 0;
}
}
function main() {
let arr = [5, 8, 2, 1, 10, 9];
let n = arr.length;
let dp = Array.from({ length: n }, () => Array(n).fill(-1));
let ans = findWinner(0, n - 1, Number.MIN_SAFE_INTEGER, arr, dp);
if (ans) {
console.log( "First player wins" );
} else {
console.log( "Second player wins" );
}
}
main();
|
Output
Second player wins
Practice Problems on Dynamic Programming in Game Theory:
Share your thoughts in the comments
Please Login to comment...